scitex 2.8.1__py3-none-any.whl → 2.10.2__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 (415) hide show
  1. scitex/__init__.py +15 -7
  2. scitex/__version__.py +1 -2
  3. scitex/_install_guide.py +250 -0
  4. scitex/_optional_deps.py +206 -39
  5. scitex/ai/_gen_ai/_Groq.py +2 -4
  6. scitex/ai/_gen_ai/_OpenAI.py +5 -2
  7. scitex/ai/_gen_ai/_Perplexity.py +20 -6
  8. scitex/audio/__init__.py +24 -15
  9. scitex/audio/_cross_process_lock.py +139 -0
  10. scitex/audio/_mcp_handlers.py +256 -0
  11. scitex/audio/_mcp_tool_schemas.py +203 -0
  12. scitex/audio/engines/elevenlabs_engine.py +5 -2
  13. scitex/audio/mcp_server.py +98 -457
  14. scitex/bridge/__init__.py +30 -19
  15. scitex/bridge/_figrecipe.py +245 -0
  16. scitex/bridge/_helpers.py +2 -1
  17. scitex/bridge/_plt_vis.py +23 -10
  18. scitex/bridge/_stats_plt.py +18 -5
  19. scitex/bridge/_stats_vis.py +16 -2
  20. scitex/browser/__init__.py +84 -44
  21. scitex/browser/automation/__init__.py +5 -1
  22. scitex/browser/core/BrowserMixin.py +17 -4
  23. scitex/browser/core/__init__.py +11 -2
  24. scitex/browser/remote/CaptchaHandler.py +1 -1
  25. scitex/browser/remote/ZenRowsAPIClient.py +1 -1
  26. scitex/capture/grid.py +487 -0
  27. scitex/capture/mcp_handlers.py +401 -0
  28. scitex/capture/mcp_tool_defs.py +192 -0
  29. scitex/capture/mcp_tools.py +241 -0
  30. scitex/capture/mcp_utils.py +30 -0
  31. scitex/cli/convert.py +421 -0
  32. scitex/cli/main.py +6 -4
  33. scitex/datetime/__init__.py +46 -0
  34. scitex/datetime/_linspace.py +100 -0
  35. scitex/datetime/_normalize_timestamp.py +306 -0
  36. scitex/db/_delete_duplicates.py +4 -4
  37. scitex/db/_sqlite3/_delete_duplicates.py +11 -2
  38. scitex/dev/plt/__init__.py +61 -62
  39. scitex/dev/plt/demo_plotters/__init__.py +0 -0
  40. scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
  41. scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
  42. scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
  43. scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
  44. scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
  45. scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
  46. scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
  47. scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
  48. scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
  49. scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
  50. scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
  51. scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
  52. scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
  53. scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
  54. scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
  55. scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
  56. scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
  57. scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
  58. scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
  59. scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
  60. scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
  61. scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
  62. scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
  63. scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
  64. scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
  65. scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
  66. scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
  67. scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
  68. scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
  69. scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
  70. scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
  71. scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
  72. scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
  73. scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
  74. scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
  75. scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
  76. scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
  77. scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
  78. scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
  79. scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
  80. scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
  81. scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
  82. scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
  83. scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
  84. scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
  85. scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
  86. scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
  87. scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
  88. scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
  89. scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
  90. scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
  91. scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
  92. scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
  93. scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
  94. scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
  95. scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
  96. scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
  97. scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
  98. scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
  99. scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
  100. scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
  101. scitex/dev/plt/mpl/get_dir_ax.py +46 -0
  102. scitex/dev/plt/mpl/get_signatures.py +176 -0
  103. scitex/dev/plt/mpl/get_signatures_details.py +522 -0
  104. scitex/dict/_pop_keys.py +1 -7
  105. scitex/dsp/__init__.py +15 -10
  106. scitex/dsp/add_noise.py +5 -2
  107. scitex/dsp/example.py +35 -22
  108. scitex/dsp/filt.py +8 -3
  109. scitex/dsp/reference.py +3 -2
  110. scitex/dsp/utils/__init__.py +2 -1
  111. scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
  112. scitex/dt/__init__.py +39 -2
  113. scitex/errors.py +82 -521
  114. scitex/fig/__init__.py +4 -4
  115. scitex/fig/editor/edit/panel_loader.py +1 -1
  116. scitex/fig/io/_bundle.py +7 -7
  117. scitex/fts/README.md +262 -0
  118. scitex/fts/TODO.md +66 -0
  119. scitex/fts/__init__.py +90 -0
  120. scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
  121. scitex/fts/_bundle/_FTS.py +657 -0
  122. scitex/fts/_bundle/__init__.py +38 -0
  123. scitex/fts/_bundle/_children.py +216 -0
  124. scitex/fts/_bundle/_conversion/__init__.py +15 -0
  125. scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
  126. scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
  127. scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
  128. scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
  129. scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
  130. scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
  131. scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
  132. scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
  133. scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
  134. scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
  135. scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
  136. scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
  137. scitex/fts/_bundle/_extractors/__init__.py +32 -0
  138. scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
  139. scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
  140. scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
  141. scitex/fts/_bundle/_loader.py +134 -0
  142. scitex/fts/_bundle/_mpl_helpers.py +389 -0
  143. scitex/fts/_bundle/_saver.py +269 -0
  144. scitex/fts/_bundle/_storage.py +200 -0
  145. scitex/fts/_bundle/_utils/__init__.py +55 -0
  146. scitex/fts/_bundle/_utils/_const.py +26 -0
  147. scitex/fts/_bundle/_utils/_errors.py +73 -0
  148. scitex/fts/_bundle/_utils/_generate.py +21 -0
  149. scitex/fts/_bundle/_utils/_types.py +76 -0
  150. scitex/fts/_bundle/_validation.py +434 -0
  151. scitex/fts/_bundle/_zipbundle.py +165 -0
  152. scitex/fts/_fig/__init__.py +22 -0
  153. scitex/fts/_fig/_backend/__init__.py +53 -0
  154. scitex/fts/_fig/_backend/_export.py +165 -0
  155. scitex/fts/_fig/_backend/_parser.py +188 -0
  156. scitex/fts/_fig/_backend/_render.py +538 -0
  157. scitex/fts/_fig/_composite.py +345 -0
  158. scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
  159. scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
  160. scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
  161. scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
  162. scitex/fts/_fig/_dataclasses/__init__.py +47 -0
  163. scitex/fts/_fig/_editor/__init__.py +14 -0
  164. scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
  165. scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
  166. scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
  167. scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
  168. scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
  169. scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
  170. scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
  171. scitex/fts/_fig/_editor/_defaults.py +300 -0
  172. scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
  173. scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
  174. scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
  175. scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
  176. scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
  177. scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
  178. scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
  179. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
  180. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
  181. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
  182. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
  183. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
  184. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
  185. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
  186. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
  187. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
  188. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
  189. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
  190. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
  191. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
  192. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
  193. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
  194. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
  195. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
  196. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
  197. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
  198. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
  199. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
  200. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
  201. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
  202. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
  203. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
  204. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
  205. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
  206. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
  207. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
  208. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
  209. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
  210. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
  211. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
  212. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
  213. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
  214. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
  215. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
  216. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
  217. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
  218. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
  219. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
  220. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
  221. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
  222. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
  223. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
  224. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
  225. scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
  226. scitex/fts/_fig/_models/_Annotations.py +115 -0
  227. scitex/fts/_fig/_models/_Axes.py +152 -0
  228. scitex/fts/_fig/_models/_Figure.py +138 -0
  229. scitex/fts/_fig/_models/_Guides.py +104 -0
  230. scitex/fts/_fig/_models/_Plot.py +123 -0
  231. scitex/fts/_fig/_models/_Styles.py +245 -0
  232. scitex/fts/_fig/_models/__init__.py +80 -0
  233. scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
  234. scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
  235. scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
  236. scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
  237. scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
  238. scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
  239. scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
  240. scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
  241. scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
  242. scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
  243. scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
  244. scitex/fts/_fig/_utils/__init__.py +129 -0
  245. scitex/fts/_fig/_utils/_auto_layout.py +127 -0
  246. scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
  247. scitex/fts/_fig/_utils/_const_sizes.py +48 -0
  248. scitex/fts/_fig/_utils/_convert_coords.py +77 -0
  249. scitex/fts/_fig/_utils/_get_template.py +178 -0
  250. scitex/fts/_fig/_utils/_normalize.py +73 -0
  251. scitex/fts/_fig/_utils/_plot_layout.py +397 -0
  252. scitex/fts/_fig/_utils/_validate.py +197 -0
  253. scitex/fts/_kinds/__init__.py +45 -0
  254. scitex/fts/_kinds/_figure/__init__.py +19 -0
  255. scitex/fts/_kinds/_figure/_composite.py +345 -0
  256. scitex/fts/_kinds/_plot/__init__.py +25 -0
  257. scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
  258. scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
  259. scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
  260. scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
  261. scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
  262. scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
  263. scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
  264. scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
  265. scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
  266. scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
  267. scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
  268. scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
  269. scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
  270. scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
  271. scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
  272. scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
  273. scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
  274. scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
  275. scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
  276. scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
  277. scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
  278. scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
  279. scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
  280. scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
  281. scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
  282. scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
  283. scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
  284. scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
  285. scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
  286. scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
  287. scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
  288. scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
  289. scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
  290. scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
  291. scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
  292. scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
  293. scitex/fts/_kinds/_shape/__init__.py +141 -0
  294. scitex/fts/_kinds/_stats/__init__.py +56 -0
  295. scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
  296. scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
  297. scitex/fts/_kinds/_table/__init__.py +72 -0
  298. scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
  299. scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
  300. scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
  301. scitex/fts/_kinds/_table/_latex/_export.py +279 -0
  302. scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
  303. scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
  304. scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
  305. scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
  306. scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
  307. scitex/fts/_kinds/_text/__init__.py +77 -0
  308. scitex/fts/_schemas/data_info.schema.json +75 -0
  309. scitex/fts/_schemas/encoding.schema.json +90 -0
  310. scitex/fts/_schemas/node.schema.json +145 -0
  311. scitex/fts/_schemas/render_manifest.schema.json +62 -0
  312. scitex/fts/_schemas/stats.schema.json +132 -0
  313. scitex/fts/_schemas/theme.schema.json +141 -0
  314. scitex/fts/_stats/__init__.py +48 -0
  315. scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
  316. scitex/fts/_stats/_dataclasses/__init__.py +48 -0
  317. scitex/fts/_tables/__init__.py +65 -0
  318. scitex/fts/_tables/_latex/__init__.py +93 -0
  319. scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
  320. scitex/fts/_tables/_latex/_editor/_app.py +725 -0
  321. scitex/fts/_tables/_latex/_export.py +279 -0
  322. scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
  323. scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
  324. scitex/fts/_tables/_latex/_table_exporter.py +362 -0
  325. scitex/fts/_tables/_latex/_utils.py +369 -0
  326. scitex/fts/_tables/_latex/_validator.py +445 -0
  327. scitex/gen/__init__.py +66 -25
  328. scitex/gen/misc.py +28 -0
  329. scitex/io/__init__.py +47 -32
  330. scitex/io/_load.py +87 -36
  331. scitex/io/_load_modules/__init__.py +10 -7
  332. scitex/io/_load_modules/_pandas.py +6 -1
  333. scitex/io/_save.py +299 -1556
  334. scitex/io/_save_modules/__init__.py +76 -19
  335. scitex/io/_save_modules/_figure_utils.py +90 -0
  336. scitex/io/_save_modules/_image_csv.py +497 -0
  337. scitex/io/_save_modules/_legends.py +91 -0
  338. scitex/io/_save_modules/_pltz_bundle.py +356 -0
  339. scitex/io/_save_modules/_pltz_stx.py +536 -0
  340. scitex/io/_save_modules/_stx_bundle.py +104 -0
  341. scitex/io/_save_modules/_symlink.py +96 -0
  342. scitex/io/_save_modules/_yaml.py +1 -1
  343. scitex/io/_save_modules/_zarr.py +64 -18
  344. scitex/io/bundle/README.md +212 -0
  345. scitex/io/bundle/__init__.py +110 -0
  346. scitex/io/{_bundle.py → bundle/_core.py} +168 -97
  347. scitex/io/bundle/_nested.py +713 -0
  348. scitex/io/bundle/_types.py +74 -0
  349. scitex/io/{_zip_bundle.py → bundle/_zip.py} +93 -45
  350. scitex/io/utils/h5_to_zarr.py +1 -1
  351. scitex/logging/__init__.py +108 -13
  352. scitex/logging/_errors.py +508 -0
  353. scitex/logging/_formatters.py +30 -6
  354. scitex/logging/_warnings.py +261 -0
  355. scitex/plt/__init__.py +4 -1
  356. scitex/plt/_figrecipe.py +236 -0
  357. scitex/plt/_subplots/_AxisWrapper.py +6 -0
  358. scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
  359. scitex/plt/_subplots/_FigWrapper.py +15 -0
  360. scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
  361. scitex/plt/_subplots/_export_as_csv.py +11 -0
  362. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
  363. scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
  364. scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
  365. scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
  366. scitex/plt/_subplots/_fonts.py +71 -0
  367. scitex/plt/_subplots/_mm_layout.py +282 -0
  368. scitex/plt/gallery/__init__.py +99 -2
  369. scitex/plt/styles/_plot_postprocess.py +3 -1
  370. scitex/plt/utils/_configure_mpl.py +16 -19
  371. scitex/repro/_RandomStateManager.py +13 -8
  372. scitex/resource/__init__.py +19 -1
  373. scitex/resource/_utils/_get_env_info.py +13 -25
  374. scitex/schema/__init__.py +149 -160
  375. scitex/schema/_encoding.py +273 -0
  376. scitex/schema/_figure_elements.py +406 -0
  377. scitex/schema/_theme.py +360 -0
  378. scitex/schema/_validation.py +0 -98
  379. scitex/scholar/__init__.py +56 -14
  380. scitex/scholar/auth/ScholarAuthManager.py +1 -1
  381. scitex/scholar/auth/__init__.py +11 -2
  382. scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
  383. scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
  384. scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
  385. scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
  386. scitex/scholar/config/ScholarConfig.py +1 -1
  387. scitex/scholar/core/Scholar.py +1 -1
  388. scitex/session/_decorator.py +18 -16
  389. scitex/session/_lifecycle.py +9 -11
  390. scitex/session/template.py +9 -8
  391. scitex/sh/test_sh.py +72 -0
  392. scitex/sh/test_sh_simple.py +61 -0
  393. scitex/stats/__init__.py +221 -97
  394. scitex/stats/_schema.py +21 -22
  395. scitex/stats/descriptive/_circular.py +212 -351
  396. scitex/stats/descriptive/_describe.py +81 -132
  397. scitex/stats/descriptive/_nan.py +205 -433
  398. scitex/stats/descriptive/_real.py +127 -141
  399. scitex/str/_format_plot_text.py +5 -5
  400. scitex/str/_latex.py +26 -84
  401. scitex/str/_latex_fallback.py +53 -47
  402. scitex/web/_search_pubmed.py +5 -4
  403. scitex/writer/tests/test_diff_between.py +451 -0
  404. scitex/writer/tests/test_document_section.py +311 -0
  405. scitex/writer/tests/test_document_workflow.py +393 -0
  406. scitex/writer/tests/test_writer.py +361 -0
  407. scitex/writer/tests/test_writer_integration.py +303 -0
  408. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/METADATA +368 -183
  409. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/RECORD +412 -97
  410. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
  411. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
  412. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
  413. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/WHEEL +0 -0
  414. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/entry_points.txt +0 -0
  415. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Overlay Rendering
3
+ * Handles SVG overlay for element highlighting and debug visualization
4
+ */
5
+
6
+ // ============================================================================
7
+ // Main Overlay Update
8
+ // ============================================================================
9
+ function updateOverlay() {
10
+ const overlay = document.getElementById('hover-overlay');
11
+ // Find the visible preview element (SVG wrapper or img)
12
+ const svgWrapper = document.getElementById('preview-svg-wrapper');
13
+ const imgEl = document.getElementById('preview');
14
+
15
+ let targetEl = null;
16
+ if (svgWrapper) {
17
+ targetEl = svgWrapper.querySelector('svg') || svgWrapper;
18
+ } else if (imgEl && imgEl.offsetWidth > 0) {
19
+ targetEl = imgEl;
20
+ }
21
+
22
+ if (!targetEl) return;
23
+
24
+ const rect = targetEl.getBoundingClientRect();
25
+ if (rect.width === 0 || rect.height === 0) return;
26
+
27
+ // Guard against zero imgSize (can cause Infinity scale)
28
+ if (!imgSize || !imgSize.width || !imgSize.height || imgSize.width === 0 || imgSize.height === 0) {
29
+ return;
30
+ }
31
+
32
+ overlay.setAttribute('width', rect.width);
33
+ overlay.setAttribute('height', rect.height);
34
+ overlay.style.width = rect.width + 'px';
35
+ overlay.style.height = rect.height + 'px';
36
+
37
+ // Position overlay over the target element
38
+ const containerRect = document.getElementById('preview-container').getBoundingClientRect();
39
+ overlay.style.left = (rect.left - containerRect.left) + 'px';
40
+ overlay.style.top = (rect.top - containerRect.top) + 'px';
41
+
42
+ const scaleX = rect.width / imgSize.width;
43
+ const scaleY = rect.height / imgSize.height;
44
+
45
+ let svg = '';
46
+
47
+ // Debug mode: draw ALL bboxes
48
+ if (debugMode) {
49
+ svg += drawDebugBboxes(scaleX, scaleY);
50
+ }
51
+
52
+ function drawElement(elementName, type) {
53
+ const bbox = elementBboxes[elementName];
54
+ if (!bbox) return '';
55
+
56
+ const elementType = bbox.element_type || '';
57
+ const hasPoints = bbox.points && bbox.points.length > 0;
58
+
59
+ // Lines - draw as path
60
+ if ((elementType === 'line' || elementName.includes('trace_')) && hasPoints) {
61
+ return drawTracePath(bbox, scaleX, scaleY, type);
62
+ }
63
+ // Scatter - draw as circles
64
+ else if (elementType === 'scatter' && hasPoints) {
65
+ return drawScatterPoints(bbox, scaleX, scaleY, type);
66
+ }
67
+ // Default - draw bbox rectangle
68
+ else {
69
+ const rectClass = type === 'hover' ? 'hover-rect' : 'selected-rect';
70
+ const labelClass = type === 'hover' ? 'hover-label' : 'selected-label';
71
+ const x = bbox.x0 * scaleX - 2;
72
+ const y = bbox.y0 * scaleY - 2;
73
+ const w = (bbox.x1 - bbox.x0) * scaleX + 4;
74
+ const h = (bbox.y1 - bbox.y0) * scaleY + 4;
75
+ return `<rect class="${rectClass}" x="${x}" y="${y}" width="${w}" height="${h}" rx="2"/>` +
76
+ `<text class="${labelClass}" x="${x}" y="${y - 4}">${bbox.label || elementName}</text>`;
77
+ }
78
+ }
79
+
80
+ if (hoveredElement && hoveredElement !== selectedElement) {
81
+ svg += drawElement(hoveredElement, 'hover');
82
+ }
83
+
84
+ if (selectedElement) {
85
+ svg += drawElement(selectedElement, 'selected');
86
+ }
87
+
88
+ overlay.innerHTML = svg;
89
+ }
90
+
91
+ // ============================================================================
92
+ // Draw Path/Line Elements
93
+ // ============================================================================
94
+ function drawTracePath(bbox, scaleX, scaleY, type) {
95
+ if (!Array.isArray(bbox.points) || bbox.points.length < 2) return '';
96
+
97
+ const points = bbox.points.filter(pt => Array.isArray(pt) && pt.length >= 2);
98
+ if (points.length < 2) return '';
99
+
100
+ let pathD = `M ${points[0][0] * scaleX} ${points[0][1] * scaleY}`;
101
+ for (let i = 1; i < points.length; i++) {
102
+ pathD += ` L ${points[i][0] * scaleX} ${points[i][1] * scaleY}`;
103
+ }
104
+
105
+ const className = type === 'hover' ? 'hover-path' : 'selected-path';
106
+ const labelX = points[0][0] * scaleX;
107
+ const labelY = points[0][1] * scaleY - 8;
108
+ const labelClass = type === 'hover' ? 'hover-label' : 'selected-label';
109
+
110
+ return `<path class="${className}" d="${pathD}"/>` +
111
+ `<text class="${labelClass}" x="${labelX}" y="${labelY}">${bbox.label || ''}</text>`;
112
+ }
113
+
114
+ // ============================================================================
115
+ // Draw Scatter Points
116
+ // ============================================================================
117
+ function drawScatterPoints(bbox, scaleX, scaleY, type) {
118
+ // Draw scatter points as circles
119
+ if (!Array.isArray(bbox.points) || bbox.points.length === 0) return '';
120
+
121
+ const className = type === 'hover' ? 'hover-scatter' : 'selected-scatter';
122
+ const labelClass = type === 'hover' ? 'hover-label' : 'selected-label';
123
+ const radius = 4;
124
+
125
+ let svg = '';
126
+ for (const pt of bbox.points) {
127
+ if (!Array.isArray(pt) || pt.length < 2) continue;
128
+ const [x, y] = pt;
129
+ svg += `<circle class="${className}" cx="${x * scaleX}" cy="${y * scaleY}" r="${radius}"/>`;
130
+ }
131
+
132
+ // Add label near first point
133
+ const validPoints = bbox.points.filter(pt => Array.isArray(pt) && pt.length >= 2);
134
+ if (validPoints.length > 0) {
135
+ const labelX = validPoints[0][0] * scaleX;
136
+ const labelY = validPoints[0][1] * scaleY - 10;
137
+ svg += `<text class="${labelClass}" x="${labelX}" y="${labelY}">${bbox.label || ''}</text>`;
138
+ }
139
+
140
+ return svg;
141
+ }
142
+
143
+ // ============================================================================
144
+ // Debug Visualization
145
+ // ============================================================================
146
+ function drawDebugBboxes(scaleX, scaleY) {
147
+ let svg = '';
148
+ let count = 0;
149
+
150
+ for (const [name, bbox] of Object.entries(elementBboxes)) {
151
+ if (name === '_meta') continue;
152
+
153
+ count++;
154
+ const hasPoints = bbox.points && bbox.points.length > 0;
155
+ const elementType = bbox.element_type || '';
156
+
157
+ // Choose color based on element type
158
+ let rectClass = 'debug-rect';
159
+ if (name.includes('trace_') || elementType === 'line') {
160
+ rectClass = 'debug-rect-trace';
161
+ } else if (name.includes('legend')) {
162
+ rectClass = 'debug-rect-legend';
163
+ }
164
+
165
+ // Draw bbox rectangle
166
+ const x = bbox.x0 * scaleX;
167
+ const y = bbox.y0 * scaleY;
168
+ const w = (bbox.x1 - bbox.x0) * scaleX;
169
+ const h = (bbox.y1 - bbox.y0) * scaleY;
170
+
171
+ svg += `<rect class="${rectClass}" x="${x}" y="${y}" width="${w}" height="${h}"/>`;
172
+ svg += `<text class="debug-label" x="${x + 2}" y="${y + 10}">${name}</text>`;
173
+
174
+ // Draw path points if available
175
+ if (hasPoints && bbox.points.length > 1) {
176
+ let pathD = `M ${bbox.points[0][0] * scaleX} ${bbox.points[0][1] * scaleY}`;
177
+ for (let i = 1; i < bbox.points.length; i++) {
178
+ const pt = bbox.points[i];
179
+ if (pt && pt.length >= 2) {
180
+ pathD += ` L ${pt[0] * scaleX} ${pt[1] * scaleY}`;
181
+ }
182
+ }
183
+ svg += `<path class="debug-path" d="${pathD}"/>`;
184
+ }
185
+ }
186
+
187
+ return svg;
188
+ }
189
+
190
+ function toggleDebugMode() {
191
+ debugMode = !debugMode;
192
+ const btn = document.getElementById('debug-toggle-btn');
193
+ if (btn) {
194
+ btn.classList.toggle('active', debugMode);
195
+ btn.textContent = debugMode ? 'Hide Hit Areas' : 'Show Hit Areas';
196
+ }
197
+ updateOverlay();
198
+ }
199
+
200
+ // ============================================================================
201
+ // Panel Overlay Update (for multi-panel canvas)
202
+ // ============================================================================
203
+ function updatePanelOverlay(overlay, bboxes, imgSizePanel, displayWidth, displayHeight, hovered, selected, img) {
204
+ if (!overlay) return;
205
+
206
+ overlay.innerHTML = '';
207
+
208
+ // Calculate actual rendered dimensions accounting for object-fit: contain
209
+ let scaleX, scaleY, offsetX = 0, offsetY = 0;
210
+
211
+ if (img && img.naturalWidth && img.naturalHeight) {
212
+ const dims = getObjectFitContainDimensions(img);
213
+ scaleX = dims.displayWidth / imgSizePanel.width;
214
+ scaleY = dims.displayHeight / imgSizePanel.height;
215
+ offsetX = dims.offsetX;
216
+ offsetY = dims.offsetY;
217
+
218
+ // Use container dimensions for the overlay size
219
+ overlay.setAttribute('width', img.clientWidth);
220
+ overlay.setAttribute('height', img.clientHeight);
221
+ } else {
222
+ // Fallback for backward compatibility
223
+ scaleX = displayWidth / imgSizePanel.width;
224
+ scaleY = displayHeight / imgSizePanel.height;
225
+ overlay.setAttribute('width', displayWidth);
226
+ overlay.setAttribute('height', displayHeight);
227
+ }
228
+
229
+ let svg = '';
230
+
231
+ // Debug mode: draw all bboxes (with offset for object-fit:contain letterboxing)
232
+ if (panelDebugMode) {
233
+ svg += drawPanelDebugBboxes(bboxes, scaleX, scaleY, offsetX, offsetY);
234
+ }
235
+
236
+ function drawPanelElement(elementName, type) {
237
+ const bbox = bboxes[elementName];
238
+ if (!bbox) return '';
239
+
240
+ const elementType = bbox.element_type || '';
241
+ const hasPoints = bbox.points && bbox.points.length > 0;
242
+
243
+ // Lines - draw as path (with offset)
244
+ if ((elementType === 'line' || elementName.includes('trace_')) && hasPoints) {
245
+ const points = bbox.points.filter(pt => Array.isArray(pt) && pt.length >= 2);
246
+ if (points.length < 2) return '';
247
+
248
+ let pathD = `M ${points[0][0] * scaleX + offsetX} ${points[0][1] * scaleY + offsetY}`;
249
+ for (let i = 1; i < points.length; i++) {
250
+ pathD += ` L ${points[i][0] * scaleX + offsetX} ${points[i][1] * scaleY + offsetY}`;
251
+ }
252
+
253
+ const className = type === 'hover' ? 'hover-path' : 'selected-path';
254
+ return `<path class="${className}" d="${pathD}"/>`;
255
+ }
256
+ // Scatter - draw as circles (with offset)
257
+ else if (elementType === 'scatter' && hasPoints) {
258
+ const className = type === 'hover' ? 'hover-scatter' : 'selected-scatter';
259
+ let circles = '';
260
+ for (const pt of bbox.points) {
261
+ if (!Array.isArray(pt) || pt.length < 2) continue;
262
+ const [x, y] = pt;
263
+ circles += `<circle class="${className}" cx="${x * scaleX + offsetX}" cy="${y * scaleY + offsetY}" r="3"/>`;
264
+ }
265
+ return circles;
266
+ }
267
+ // Default - draw bbox rectangle (with offset)
268
+ else {
269
+ const rectClass = type === 'hover' ? 'hover-rect' : 'selected-rect';
270
+ const x = bbox.x0 * scaleX + offsetX;
271
+ const y = bbox.y0 * scaleY + offsetY;
272
+ const w = (bbox.x1 - bbox.x0) * scaleX;
273
+ const h = (bbox.y1 - bbox.y0) * scaleY;
274
+ return `<rect class="${rectClass}" x="${x}" y="${y}" width="${w}" height="${h}" rx="1"/>`;
275
+ }
276
+ }
277
+
278
+ if (hovered && hovered !== selected) {
279
+ svg += drawPanelElement(hovered, 'hover');
280
+ }
281
+
282
+ if (selected) {
283
+ svg += drawPanelElement(selected, 'selected');
284
+ }
285
+
286
+ overlay.innerHTML = svg;
287
+ }
288
+
289
+ function drawPanelDebugBboxes(bboxes, scaleX, scaleY, offsetX, offsetY) {
290
+ let svg = '';
291
+
292
+ // Default offset to 0 if not provided
293
+ offsetX = offsetX || 0;
294
+ offsetY = offsetY || 0;
295
+
296
+ for (const [name, bbox] of Object.entries(bboxes)) {
297
+ if (name === '_meta') continue;
298
+
299
+ const hasPoints = bbox.points && bbox.points.length > 0;
300
+ const elementType = bbox.element_type || '';
301
+
302
+ // Choose color based on element type
303
+ let rectClass = 'debug-rect';
304
+ if (name.includes('trace_') || elementType === 'line') {
305
+ rectClass = 'debug-rect-trace';
306
+ } else if (name.includes('legend')) {
307
+ rectClass = 'debug-rect-legend';
308
+ }
309
+
310
+ // Draw bbox rectangle (with offset for object-fit:contain letterboxing)
311
+ const x = bbox.x0 * scaleX + offsetX;
312
+ const y = bbox.y0 * scaleY + offsetY;
313
+ const w = (bbox.x1 - bbox.x0) * scaleX;
314
+ const h = (bbox.y1 - bbox.y0) * scaleY;
315
+
316
+ svg += `<rect class="${rectClass}" x="${x}" y="${y}" width="${w}" height="${h}"/>`;
317
+
318
+ // Draw short label (truncated for small panels)
319
+ const shortName = name.length > 15 ? name.substring(0, 12) + '...' : name;
320
+ svg += `<text class="debug-label" x="${x + 2}" y="${y + 8}" font-size="8">${shortName}</text>`;
321
+
322
+ // Draw path points if available (with offset)
323
+ if (hasPoints && bbox.points.length > 1) {
324
+ let pathD = `M ${bbox.points[0][0] * scaleX + offsetX} ${bbox.points[0][1] * scaleY + offsetY}`;
325
+ for (let i = 1; i < bbox.points.length; i++) {
326
+ const pt = bbox.points[i];
327
+ if (pt && pt.length >= 2) {
328
+ pathD += ` L ${pt[0] * scaleX + offsetX} ${pt[1] * scaleY + offsetY}`;
329
+ }
330
+ }
331
+ svg += `<path class="debug-path" d="${pathD}" stroke-width="0.5"/>`;
332
+ }
333
+ }
334
+
335
+ return svg;
336
+ }
337
+
338
+ function togglePanelDebugMode() {
339
+ panelDebugMode = !panelDebugMode;
340
+ const btn = document.getElementById('panel-debug-toggle-btn');
341
+ if (btn) {
342
+ btn.classList.toggle('active', panelDebugMode);
343
+ btn.textContent = panelDebugMode ? 'Hide Panel Hit Areas' : 'Show Panel Hit Areas';
344
+ }
345
+
346
+ // Redraw all panel overlays
347
+ redrawAllPanelOverlays();
348
+ }
349
+
350
+ function redrawAllPanelOverlays() {
351
+ document.querySelectorAll('.panel-canvas-item').forEach((item, idx) => {
352
+ const panelName = item.dataset.panelName;
353
+ const overlay = item.querySelector('.panel-card-overlay');
354
+ const img = item.querySelector('img');
355
+ const panelCache = panelBboxesCache[panelName];
356
+
357
+ if (overlay && img && panelCache) {
358
+ const rect = img.getBoundingClientRect();
359
+ updatePanelOverlay(
360
+ overlay,
361
+ panelCache.bboxes,
362
+ panelCache.imgSize,
363
+ rect.width,
364
+ rect.height,
365
+ panelHoveredElement,
366
+ selectedElement,
367
+ img
368
+ );
369
+ }
370
+ });
371
+ }
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Preview Management
3
+ * Handles preview loading, panel navigation, and grid view
4
+ */
5
+
6
+ // ============================================================================
7
+ // Initial Preview Loading
8
+ // ============================================================================
9
+ async function loadInitialPreview() {
10
+ setStatus('Loading preview...', false);
11
+ try {
12
+ const resp = await fetch('/preview');
13
+ const data = await resp.json();
14
+
15
+ console.log('=== PREVIEW DATA RECEIVED ===');
16
+ console.log('format:', data.format);
17
+ console.log('img_size:', data.img_size);
18
+ console.log('bboxes keys:', Object.keys(data.bboxes || {}));
19
+
20
+ const previewContainer = document.getElementById('preview-container');
21
+ const img = document.getElementById('preview');
22
+
23
+ if (data.format === 'svg' && data.svg) {
24
+ // Handle SVG: replace img with inline SVG
25
+ const svgWrapper = document.createElement('div');
26
+ svgWrapper.id = 'preview-svg-wrapper';
27
+ svgWrapper.innerHTML = data.svg;
28
+ svgWrapper.style.width = '100%';
29
+ svgWrapper.style.maxHeight = '70vh';
30
+
31
+ // Find the SVG element and set styles
32
+ const svgEl = svgWrapper.querySelector('svg');
33
+ if (svgEl) {
34
+ svgEl.style.width = '100%';
35
+ svgEl.style.height = 'auto';
36
+ svgEl.style.maxHeight = '70vh';
37
+ svgEl.id = 'preview'; // Keep same ID for event handlers
38
+ }
39
+
40
+ img.style.display = 'none';
41
+ const existingWrapper = document.getElementById('preview-svg-wrapper');
42
+ if (existingWrapper) existingWrapper.remove();
43
+ previewContainer.appendChild(svgWrapper);
44
+ } else if (data.image) {
45
+ // Handle PNG: show as base64 image
46
+ img.src = 'data:image/png;base64,' + data.image;
47
+ img.style.display = 'block';
48
+ const existingWrapper = document.getElementById('preview-svg-wrapper');
49
+ if (existingWrapper) existingWrapper.remove();
50
+ }
51
+
52
+ if (data.bboxes) {
53
+ elementBboxes = data.bboxes;
54
+ originalBboxes = JSON.parse(JSON.stringify(data.bboxes)); // Deep copy
55
+ if (data.bboxes._meta) {
56
+ schemaMeta = data.bboxes._meta;
57
+ }
58
+ }
59
+ if (data.img_size) {
60
+ imgSize = data.img_size;
61
+ originalImgSize = {...data.img_size}; // Copy
62
+ }
63
+
64
+ isShowingOriginalPreview = true;
65
+ updateOverlay();
66
+ setStatus('Preview loaded', false);
67
+
68
+ // Initialize hover system for the SVG if needed
69
+ if (data.format === 'svg') {
70
+ const svgWrapper = document.getElementById('preview-svg-wrapper');
71
+ if (svgWrapper) {
72
+ initHoverSystemForElement(svgWrapper.querySelector('svg'));
73
+ }
74
+ }
75
+
76
+ // Draw debug bboxes if debug mode is on
77
+ if (debugMode) {
78
+ updateOverlay();
79
+ }
80
+
81
+ // Handle multi-panel figz bundles
82
+ if (data.panel_info && data.panel_info.panels) {
83
+ panelData = data.panel_info;
84
+ currentPanelIndex = data.panel_info.current_index || 0;
85
+ console.log('Multi-panel figz detected:', panelData.panels.length, 'panels');
86
+ loadPanelGrid();
87
+ }
88
+
89
+ // Start auto-update AFTER initial preview is loaded
90
+ setAutoUpdateInterval();
91
+ } catch (e) {
92
+ setStatus('Error loading preview: ' + e.message, true);
93
+ console.error('Preview load error:', e);
94
+ // Start auto-update even on error so the editor works
95
+ setAutoUpdateInterval();
96
+ }
97
+ }
98
+
99
+ // ============================================================================
100
+ // Multi-Panel Grid Loading
101
+ // ============================================================================
102
+ async function loadPanelGrid() {
103
+ if (!panelData || panelData.panels.length <= 1) {
104
+ // Not a multi-panel bundle or only one panel
105
+ document.getElementById('panel-grid-section').style.display = 'none';
106
+ document.getElementById('preview-header').style.display = 'none';
107
+ return;
108
+ }
109
+
110
+ console.log('Loading panel canvas for', panelData.panels.length, 'panels');
111
+
112
+ // Hide single-panel preview completely for multi-panel bundles (unified canvas only)
113
+ document.getElementById('preview-header').style.display = 'none';
114
+ const previewWrapper = document.querySelector('.preview-wrapper');
115
+ if (previewWrapper) {
116
+ previewWrapper.style.display = 'none';
117
+ }
118
+
119
+ // Fetch all panel images with bboxes
120
+ try {
121
+ const resp = await fetch('/panels');
122
+ const data = await resp.json();
123
+
124
+ if (data.error) {
125
+ console.error('Panel canvas error:', data.error);
126
+ return;
127
+ }
128
+
129
+ const canvasEl = document.getElementById('panel-canvas');
130
+ canvasEl.innerHTML = '';
131
+
132
+ // Use figz layout to position panels as unified canvas (matching export)
133
+ const hasLayout = data.layout && Object.keys(data.layout).length > 0;
134
+
135
+ // Calculate scale factor: convert mm to pixels
136
+ // Find total figure dimensions from layout
137
+ let maxX = 0, maxY = 0;
138
+ if (hasLayout) {
139
+ Object.values(data.layout).forEach(l => {
140
+ const right = (l.position?.x_mm || 0) + (l.size?.width_mm || 80);
141
+ const bottom = (l.position?.y_mm || 0) + (l.size?.height_mm || 50);
142
+ maxX = Math.max(maxX, right);
143
+ maxY = Math.max(maxY, bottom);
144
+ });
145
+ }
146
+
147
+ // Scale to fit canvas (max width ~700px for good display)
148
+ const canvasMaxWidth = 700;
149
+ const scale = hasLayout && maxX > 0 ? canvasMaxWidth / maxX : 3; // ~3px per mm fallback
150
+ canvasScale = scale; // Store globally for drag conversions
151
+
152
+ // Reset layout tracking
153
+ panelLayoutMm = {};
154
+ layoutModified = false;
155
+
156
+ data.panels.forEach((panel, idx) => {
157
+ // Store bboxes and imgSize in cache for interactive hover/click
158
+ if (panel.bboxes && panel.img_size) {
159
+ panelBboxesCache[panel.name] = {
160
+ bboxes: panel.bboxes,
161
+ imgSize: panel.img_size
162
+ };
163
+ }
164
+
165
+ // Use figz layout for positioning (unified canvas like export)
166
+ let pos, posMm;
167
+ if (panel.layout && panel.layout.position && panel.layout.size) {
168
+ const x_mm = panel.layout.position.x_mm || 0;
169
+ const y_mm = panel.layout.position.y_mm || 0;
170
+ const width_mm = panel.layout.size.width_mm || 80;
171
+ const height_mm = panel.layout.size.height_mm || 50;
172
+ pos = {
173
+ x: x_mm * scale,
174
+ y: y_mm * scale,
175
+ width: width_mm * scale,
176
+ height: height_mm * scale,
177
+ };
178
+ posMm = { x_mm, y_mm, width_mm, height_mm };
179
+ } else {
180
+ // Fallback grid layout if no figz layout
181
+ const cols = Math.ceil(Math.sqrt(data.panels.length));
182
+ const baseWidth = 220, baseHeight = 180, padding = 15;
183
+ const col = idx % cols;
184
+ const row = Math.floor(idx / cols);
185
+ pos = {
186
+ x: padding + col * (baseWidth + padding),
187
+ y: padding + row * (baseHeight + padding),
188
+ width: baseWidth,
189
+ height: baseHeight,
190
+ };
191
+ // Convert to mm for fallback
192
+ posMm = {
193
+ x_mm: pos.x / scale,
194
+ y_mm: pos.y / scale,
195
+ width_mm: pos.width / scale,
196
+ height_mm: pos.height / scale,
197
+ };
198
+ }
199
+ panelPositions[panel.name] = pos;
200
+ panelLayoutMm[panel.name] = posMm;
201
+
202
+ const item = document.createElement('div');
203
+ item.className = 'panel-canvas-item' + (idx === currentPanelIndex ? ' active' : '');
204
+ item.dataset.panelIndex = idx;
205
+ item.dataset.panelName = panel.name;
206
+ item.style.left = pos.x + 'px';
207
+ item.style.top = pos.y + 'px';
208
+ item.style.width = pos.width + 'px';
209
+ item.style.height = pos.height + 'px';
210
+
211
+ if (panel.image) {
212
+ item.innerHTML = `
213
+ <span class="panel-canvas-label">${panel.name}</span>
214
+ <span class="panel-position-indicator" id="pos-${panel.name}"></span>
215
+ <div class="panel-drag-handle" title="Drag to move panel">⋮⋮</div>
216
+ <div class="panel-card-container">
217
+ <img src="data:image/png;base64,${panel.image}" alt="Panel ${panel.name}">
218
+ <svg class="panel-card-overlay" id="panel-overlay-${idx}"></svg>
219
+ </div>
220
+ `;
221
+ } else {
222
+ item.innerHTML = `
223
+ <span class="panel-canvas-label">${panel.name}</span>
224
+ <span class="panel-position-indicator" id="pos-${panel.name}"></span>
225
+ <div class="panel-drag-handle" title="Drag to move panel">⋮⋮</div>
226
+ <div style="padding: 20px; color: var(--text-muted);">No preview</div>
227
+ `;
228
+ }
229
+
230
+ // Add interactive event handlers (hover, click for element selection)
231
+ // Note: initCanvasItemInteraction already calls initPanelDrag internally
232
+ initCanvasItemInteraction(item, idx, panel.name);
233
+
234
+ canvasEl.appendChild(item);
235
+ });
236
+
237
+ // Update canvas size to fit all panels (unified canvas)
238
+ const canvasHeight = Math.max(...Object.values(panelPositions).map(p => p.y + p.height)) + 10;
239
+ const canvasWidth = Math.max(...Object.values(panelPositions).map(p => p.x + p.width)) + 10;
240
+ canvasEl.style.minHeight = Math.max(400, canvasHeight) + 'px';
241
+ canvasEl.style.minWidth = canvasWidth + 'px';
242
+
243
+ // Update panel indicator
244
+ updatePanelIndicator();
245
+
246
+ // Show unified canvas for multi-panel figures
247
+ showingPanelGrid = true;
248
+ document.getElementById('panel-grid-section').style.display = 'block';
249
+ } catch (e) {
250
+ console.error('Error loading panels:', e);
251
+ }
252
+ }
253
+
254
+ // ============================================================================
255
+ // Panel Navigation
256
+ // ============================================================================
257
+ function togglePanelGrid() {
258
+ showingPanelGrid = !showingPanelGrid;
259
+ if (showingPanelGrid) {
260
+ loadPanelGrid();
261
+ } else {
262
+ document.getElementById('panel-grid-section').style.display = 'none';
263
+ }
264
+ }
265
+
266
+ function prevPanel() {
267
+ if (currentPanelIndex > 0) {
268
+ loadPanelForEditing(panelData.panels[currentPanelIndex - 1].name);
269
+ }
270
+ }
271
+
272
+ function nextPanel() {
273
+ if (currentPanelIndex < panelData.panels.length - 1) {
274
+ loadPanelForEditing(panelData.panels[currentPanelIndex + 1].name);
275
+ }
276
+ }
277
+
278
+ function updatePanelIndicator() {
279
+ if (!panelData) return;
280
+
281
+ const indicator = document.getElementById('panel-indicator');
282
+ const prevBtn = document.getElementById('btn-prev-panel');
283
+ const nextBtn = document.getElementById('btn-next-panel');
284
+
285
+ // Update indicator text (if elements exist)
286
+ if (indicator) {
287
+ indicator.textContent = `${currentPanelIndex + 1} / ${panelData.panels.length}`;
288
+ }
289
+
290
+ // Update button states
291
+ if (prevBtn) prevBtn.disabled = currentPanelIndex === 0;
292
+ if (nextBtn) nextBtn.disabled = currentPanelIndex === panelData.panels.length - 1;
293
+ }