scitex 2.10.3__py3-none-any.whl → 2.13.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 (504) hide show
  1. scitex/__init__.py +1 -4
  2. scitex/__main__.py +24 -5
  3. scitex/__version__.py +1 -1
  4. scitex/_install_guide.py +14 -2
  5. scitex/_optional_deps.py +33 -0
  6. scitex/ai/classification/reporters/_ClassificationReporter.py +1 -1
  7. scitex/ai/classification/timeseries/_TimeSeriesBlockingSplit.py +2 -2
  8. scitex/ai/classification/timeseries/_TimeSeriesCalendarSplit.py +2 -2
  9. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +2 -2
  10. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit_v01-not-using-n_splits.py +2 -2
  11. scitex/ai/classification/timeseries/_TimeSeriesStratifiedSplit.py +2 -2
  12. scitex/ai/classification/timeseries/_normalize_timestamp.py +1 -1
  13. scitex/ai/metrics/_calc_seizure_prediction_metrics.py +1 -1
  14. scitex/ai/plt/_plot_feature_importance.py +1 -1
  15. scitex/ai/plt/_plot_learning_curve.py +1 -1
  16. scitex/ai/plt/_plot_optuna_study.py +1 -1
  17. scitex/ai/plt/_plot_pre_rec_curve.py +1 -1
  18. scitex/ai/plt/_plot_roc_curve.py +1 -1
  19. scitex/ai/plt/_stx_conf_mat.py +1 -1
  20. scitex/ai/training/_LearningCurveLogger.py +1 -1
  21. scitex/audio/mcp_server.py +38 -8
  22. scitex/bridge/_figrecipe.py +1 -1
  23. scitex/bridge/_helpers.py +1 -1
  24. scitex/bridge/_plt_vis.py +1 -1
  25. scitex/bridge/_stats_plt.py +1 -1
  26. scitex/bridge/_stats_vis.py +2 -2
  27. scitex/browser/automation/CookieHandler.py +1 -1
  28. scitex/browser/core/BrowserMixin.py +1 -1
  29. scitex/browser/core/ChromeProfileManager.py +1 -1
  30. scitex/browser/debugging/_browser_logger.py +1 -1
  31. scitex/browser/debugging/_highlight_element.py +1 -1
  32. scitex/browser/debugging/_show_grid.py +1 -1
  33. scitex/browser/interaction/click_center.py +1 -1
  34. scitex/browser/interaction/click_with_fallbacks.py +1 -1
  35. scitex/browser/interaction/close_popups.py +1 -1
  36. scitex/browser/interaction/fill_with_fallbacks.py +1 -1
  37. scitex/browser/pdf/click_download_for_chrome_pdf_viewer.py +1 -1
  38. scitex/browser/pdf/detect_chrome_pdf_viewer.py +1 -1
  39. scitex/browser/stealth/HumanBehavior.py +1 -1
  40. scitex/browser/stealth/StealthManager.py +1 -1
  41. scitex/{fig → canvas}/__init__.py +84 -96
  42. scitex/canvas/_mcp_handlers.py +372 -0
  43. scitex/canvas/_mcp_tool_schemas.py +219 -0
  44. scitex/{fig → canvas}/backend/_parser.py +1 -1
  45. scitex/{fig → canvas}/canvas.py +13 -14
  46. scitex/{fts/_fig/_editor → canvas/editor}/_defaults.py +2 -2
  47. scitex/{fig → canvas}/editor/edit/__init__.py +11 -14
  48. scitex/{fig → canvas}/editor/edit/bundle_resolver.py +56 -48
  49. scitex/{fig → canvas}/editor/edit/editor_launcher.py +79 -26
  50. scitex/{fts/_fig/_editor/_cui/_panel_loader.py → canvas/editor/edit/panel_loader.py} +8 -8
  51. scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_bbox.py +2 -1
  52. scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_core.py +84 -84
  53. scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_renderer.py +7 -6
  54. scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/static/css/features/canvas.css +2 -2
  55. scitex/{fig → canvas}/editor/flask_editor/static/css/features/panel-grid.css +1 -1
  56. scitex/{fig → canvas}/editor/flask_editor/static/js/core/api.js +3 -4
  57. scitex/{fig → canvas}/editor/flask_editor/static/js/editor/preview.js +5 -5
  58. scitex/{fig → canvas}/editor/flask_editor/templates/_html.py +3 -3
  59. scitex/{fig → canvas}/editor/flask_editor/templates/_scripts.py +10 -10
  60. scitex/{fig → canvas}/editor/flask_editor/templates/_styles.py +3 -3
  61. scitex/{fig → canvas}/io/__init__.py +32 -38
  62. scitex/{fig → canvas}/io/_bundle.py +217 -154
  63. scitex/{fig → canvas}/io/_canvas.py +1 -1
  64. scitex/{fig → canvas}/io/_data.py +1 -1
  65. scitex/{fig → canvas}/io/_export.py +1 -1
  66. scitex/{fig → canvas}/io/_load.py +1 -1
  67. scitex/{fig → canvas}/io/_panel.py +1 -1
  68. scitex/{fig → canvas}/io/_save.py +1 -1
  69. scitex/canvas/mcp_server.py +151 -0
  70. scitex/{fig → canvas}/model/__init__.py +1 -1
  71. scitex/{fig → canvas}/model/_annotations.py +1 -1
  72. scitex/{fig → canvas}/model/_axes.py +1 -1
  73. scitex/{fig → canvas}/model/_figure.py +1 -1
  74. scitex/{fig → canvas}/model/_guides.py +1 -1
  75. scitex/{fig → canvas}/model/_plot.py +1 -1
  76. scitex/{fig → canvas}/model/_styles.py +1 -1
  77. scitex/{fig → canvas}/utils/__init__.py +1 -1
  78. scitex/capture/mcp_server.py +41 -12
  79. scitex/cli/audio.py +233 -0
  80. scitex/cli/capture.py +307 -0
  81. scitex/cli/convert.py +10 -6
  82. scitex/cli/main.py +27 -4
  83. scitex/cli/repro.py +233 -0
  84. scitex/cli/resource.py +240 -0
  85. scitex/cli/stats.py +325 -0
  86. scitex/cli/template.py +236 -0
  87. scitex/cli/tex.py +286 -0
  88. scitex/cli/web.py +11 -12
  89. scitex/dev/__init__.py +3 -0
  90. scitex/dev/_pyproject.py +405 -0
  91. scitex/dev/plt/__init__.py +2 -2
  92. scitex/dev/plt/mpl/get_dir_ax.py +1 -1
  93. scitex/dev/plt/mpl/get_signatures.py +1 -1
  94. scitex/dev/plt/mpl/get_signatures_details.py +1 -1
  95. scitex/diagram/README.md +7 -7
  96. scitex/diagram/_mcp_handlers.py +400 -0
  97. scitex/diagram/_mcp_tool_schemas.py +157 -0
  98. scitex/diagram/mcp_server.py +151 -0
  99. scitex/dsp/_demo_sig.py +51 -5
  100. scitex/dsp/_mne.py +13 -2
  101. scitex/dsp/_modulation_index.py +15 -3
  102. scitex/dsp/_pac.py +23 -5
  103. scitex/dsp/_psd.py +16 -4
  104. scitex/dsp/_resample.py +24 -4
  105. scitex/dsp/_transform.py +16 -3
  106. scitex/dsp/add_noise.py +15 -1
  107. scitex/dsp/norm.py +17 -2
  108. scitex/dsp/reference.py +17 -1
  109. scitex/dsp/utils/_differential_bandpass_filters.py +20 -2
  110. scitex/dsp/utils/_zero_pad.py +18 -4
  111. scitex/dt/_normalize_timestamp.py +1 -1
  112. scitex/git/_session.py +1 -1
  113. scitex/io/__init__.py +7 -19
  114. scitex/io/_load.py +15 -19
  115. scitex/io/_load_modules/_canvas.py +2 -2
  116. scitex/io/_load_modules/_con.py +17 -6
  117. scitex/io/_load_modules/_eeg.py +28 -13
  118. scitex/io/_load_modules/_optuna.py +21 -63
  119. scitex/io/_load_modules/_torch.py +11 -3
  120. scitex/io/_save.py +11 -16
  121. scitex/io/_save_modules/__init__.py +6 -10
  122. scitex/io/_save_modules/_canvas.py +3 -3
  123. scitex/io/_save_modules/_optuna_study_as_csv_and_pngs.py +13 -2
  124. scitex/io/_save_modules/_plot_bundle.py +112 -0
  125. scitex/io/_save_modules/{_pltz_stx.py → _plot_scitex.py} +7 -7
  126. scitex/io/_save_modules/_stx_bundle.py +16 -16
  127. scitex/io/_save_modules/_torch.py +11 -3
  128. scitex/io/bundle/README.md +89 -80
  129. scitex/{fts/_bundle/_FTS.py → io/bundle/_Bundle.py} +197 -95
  130. scitex/io/bundle/__init__.py +67 -35
  131. scitex/{fts/_bundle → io/bundle}/_children.py +32 -40
  132. scitex/io/bundle/_core.py +184 -97
  133. scitex/{fts/_bundle/_dataclasses/_Node.py → io/bundle/_dataclasses/_Spec.py} +29 -23
  134. scitex/{fts/_bundle/_dataclasses/_NodeRefs.py → io/bundle/_dataclasses/_SpecRefs.py} +6 -6
  135. scitex/{fts/_bundle → io/bundle}/_dataclasses/__init__.py +4 -4
  136. scitex/{fts/_bundle → io/bundle}/_loader.py +19 -19
  137. scitex/io/bundle/_manifest.py +99 -0
  138. scitex/{fts/_bundle → io/bundle}/_mpl_helpers.py +119 -28
  139. scitex/io/bundle/_nested.py +113 -100
  140. scitex/{fts/_bundle → io/bundle}/_saver.py +13 -14
  141. scitex/{fts/_bundle → io/bundle}/_storage.py +3 -3
  142. scitex/io/bundle/_types.py +41 -16
  143. scitex/{fts/_bundle → io/bundle}/_validation.py +20 -18
  144. scitex/io/bundle/_zip.py +21 -31
  145. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_backend/_parser.py +1 -1
  146. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Annotations.py +1 -1
  147. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Axes.py +1 -1
  148. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Figure.py +1 -1
  149. scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_Guides.py +1 -1
  150. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Plot.py +1 -1
  151. scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_Styles.py +1 -1
  152. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_utils/_plot_layout.py +1 -1
  153. scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/__init__.py +1 -1
  154. scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_editor/_app.py +1 -1
  155. scitex/{fts/_tables → io/bundle/kinds/_table}/_latex/_export.py +1 -1
  156. scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_figure_exporter.py +1 -1
  157. scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_table_exporter.py +1 -1
  158. scitex/io/bundle/schemas/__init__.py +30 -0
  159. scitex/mcp_server.py +159 -0
  160. scitex/parallel/_run.py +5 -4
  161. scitex/path/_find.py +60 -83
  162. scitex/path/_get_module_path.py +23 -21
  163. scitex/path/_get_spath.py +6 -27
  164. scitex/path/_getsize.py +23 -9
  165. scitex/path/_increment_version.py +31 -38
  166. scitex/path/_mk_spath.py +26 -29
  167. scitex/path/_path.py +5 -12
  168. scitex/path/_split.py +27 -15
  169. scitex/path/_this_path.py +23 -9
  170. scitex/plt/_mcp_handlers.py +361 -0
  171. scitex/plt/_mcp_tool_schemas.py +169 -0
  172. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +2 -1
  173. scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +2 -2
  174. scitex/plt/gallery/_generate.py +76 -50
  175. scitex/plt/io/__init__.py +17 -19
  176. scitex/plt/io/_bundle.py +99 -52
  177. scitex/plt/io/_layered_bundle.py +303 -168
  178. scitex/plt/mcp_server.py +205 -0
  179. scitex/plt/utils/_csv_column_naming.py +250 -118
  180. scitex/repro/README_RandomStateManager.md +3 -3
  181. scitex/repro/_RandomStateManager.py +14 -14
  182. scitex/repro/_gen_ID.py +1 -1
  183. scitex/repro/_gen_timestamp.py +1 -1
  184. scitex/repro/_hash_array.py +4 -4
  185. scitex/schema/__init__.py +69 -73
  186. scitex/schema/_canvas.py +1 -1
  187. scitex/schema/_stats.py +2 -2
  188. scitex/scholar/__main__.py +24 -2
  189. scitex/scholar/_mcp_handlers.py +685 -0
  190. scitex/scholar/_mcp_tool_schemas.py +339 -0
  191. scitex/scholar/docs/template.py +1 -1
  192. scitex/scholar/examples/07_storage_integration.py +1 -1
  193. scitex/scholar/impact_factor/jcr/ImpactFactorJCREngine.py +1 -1
  194. scitex/scholar/impact_factor/jcr/build_database.py +1 -1
  195. scitex/scholar/mcp_server.py +315 -0
  196. scitex/scholar/pdf_download/ScholarPDFDownloader.py +1 -1
  197. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +1 -1
  198. scitex/scholar/pipelines/ScholarPipelineParallel.py +1 -1
  199. scitex/scholar/pipelines/ScholarPipelineSingle.py +1 -1
  200. scitex/scholar/storage/PaperIO.py +1 -1
  201. scitex/session/README.md +4 -4
  202. scitex/session/__init__.py +1 -1
  203. scitex/session/_decorator.py +9 -9
  204. scitex/session/_lifecycle.py +5 -5
  205. scitex/session/template.py +1 -1
  206. scitex/stats/__init__.py +30 -33
  207. scitex/stats/__main__.py +281 -0
  208. scitex/stats/_mcp_handlers.py +1191 -0
  209. scitex/stats/_mcp_tool_schemas.py +384 -0
  210. scitex/stats/_schema.py +1 -1
  211. scitex/stats/correct/_correct_bonferroni.py +1 -1
  212. scitex/stats/correct/_correct_fdr.py +1 -1
  213. scitex/stats/correct/_correct_fdr_.py +1 -1
  214. scitex/stats/correct/_correct_holm.py +1 -1
  215. scitex/stats/correct/_correct_sidak.py +1 -1
  216. scitex/stats/effect_sizes/_cliffs_delta.py +1 -1
  217. scitex/stats/effect_sizes/_cohens_d.py +1 -1
  218. scitex/stats/effect_sizes/_epsilon_squared.py +1 -1
  219. scitex/stats/effect_sizes/_eta_squared.py +1 -1
  220. scitex/stats/effect_sizes/_prob_superiority.py +1 -1
  221. scitex/stats/io/__init__.py +10 -11
  222. scitex/stats/io/_bundle.py +16 -16
  223. scitex/stats/mcp_server.py +405 -0
  224. scitex/stats/posthoc/_dunnett.py +1 -1
  225. scitex/stats/posthoc/_games_howell.py +1 -1
  226. scitex/stats/posthoc/_tukey_hsd.py +1 -1
  227. scitex/stats/power/_power.py +1 -1
  228. scitex/stats/utils/_effect_size.py +1 -1
  229. scitex/stats/utils/_formatters.py +1 -1
  230. scitex/stats/utils/_power.py +1 -1
  231. scitex/template/_mcp_handlers.py +259 -0
  232. scitex/template/_mcp_tool_schemas.py +112 -0
  233. scitex/template/mcp_server.py +186 -0
  234. scitex/utils/_verify_scitex_format.py +2 -2
  235. scitex/utils/template.py +1 -1
  236. scitex/web/__init__.py +12 -11
  237. scitex/web/_scraping.py +26 -265
  238. scitex/web/download_images.py +316 -0
  239. scitex/writer/Writer.py +1 -1
  240. scitex/writer/_clone_writer_project.py +1 -1
  241. scitex/writer/_validate_tree_structures.py +1 -1
  242. scitex/writer/dataclasses/config/_WriterConfig.py +1 -1
  243. scitex/writer/dataclasses/contents/_ManuscriptContents.py +1 -1
  244. scitex/writer/dataclasses/core/_Document.py +1 -1
  245. scitex/writer/dataclasses/core/_DocumentSection.py +1 -1
  246. scitex/writer/dataclasses/results/_CompilationResult.py +1 -1
  247. scitex/writer/dataclasses/results/_LaTeXIssue.py +1 -1
  248. scitex/writer/utils/.legacy_git_retry.py +7 -5
  249. scitex/writer/utils/_parse_latex_logs.py +1 -1
  250. scitex-2.13.0.dist-info/METADATA +1231 -0
  251. {scitex-2.10.3.dist-info → scitex-2.13.0.dist-info}/RECORD +376 -470
  252. scitex-2.13.0.dist-info/entry_points.txt +11 -0
  253. scitex/fig/editor/_defaults.py +0 -300
  254. scitex/fig/editor/edit/panel_loader.py +0 -232
  255. scitex/fig/editor/flask_editor/_bbox.py +0 -1299
  256. scitex/fig/editor/flask_editor/_core.py +0 -1429
  257. scitex/fig/editor/flask_editor/_renderer.py +0 -813
  258. scitex/fig/editor/flask_editor/static/css/features/canvas.css +0 -176
  259. scitex/fts/README.md +0 -262
  260. scitex/fts/TODO.md +0 -66
  261. scitex/fts/__init__.py +0 -90
  262. scitex/fts/_bundle/README_IN_BUNDLE.md +0 -102
  263. scitex/fts/_bundle/__init__.py +0 -38
  264. scitex/fts/_bundle/_utils/__init__.py +0 -55
  265. scitex/fts/_bundle/_utils/_const.py +0 -26
  266. scitex/fts/_bundle/_utils/_errors.py +0 -73
  267. scitex/fts/_bundle/_utils/_generate.py +0 -21
  268. scitex/fts/_bundle/_utils/_types.py +0 -76
  269. scitex/fts/_bundle/_zipbundle.py +0 -165
  270. scitex/fts/_fig/__init__.py +0 -22
  271. scitex/fts/_fig/_backend/_parser.py +0 -188
  272. scitex/fts/_fig/_editor/__init__.py +0 -14
  273. scitex/fts/_fig/_editor/_cui/__init__.py +0 -33
  274. scitex/fts/_fig/_editor/_cui/_backend_detector.py +0 -39
  275. scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +0 -366
  276. scitex/fts/_fig/_editor/_cui/_editor_launcher.py +0 -175
  277. scitex/fts/_fig/_editor/_cui/_manual_handler.py +0 -52
  278. scitex/fts/_fig/_editor/_cui/_path_resolver.py +0 -66
  279. scitex/fts/_fig/_editor/_gui/__init__.py +0 -11
  280. scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +0 -20
  281. scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +0 -664
  282. scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +0 -79
  283. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +0 -41
  284. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +0 -16
  285. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +0 -85
  286. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +0 -217
  287. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +0 -93
  288. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +0 -57
  289. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +0 -112
  290. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +0 -59
  291. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +0 -212
  292. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +0 -190
  293. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +0 -59
  294. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +0 -45
  295. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +0 -95
  296. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +0 -101
  297. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +0 -138
  298. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +0 -31
  299. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +0 -7
  300. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +0 -56
  301. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +0 -78
  302. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +0 -314
  303. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +0 -107
  304. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +0 -54
  305. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +0 -172
  306. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +0 -258
  307. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +0 -48
  308. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +0 -71
  309. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +0 -288
  310. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +0 -143
  311. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +0 -245
  312. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +0 -992
  313. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +0 -339
  314. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +0 -286
  315. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +0 -371
  316. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +0 -293
  317. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +0 -426
  318. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +0 -152
  319. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +0 -265
  320. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +0 -184
  321. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +0 -57
  322. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +0 -100
  323. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +0 -34
  324. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +0 -124
  325. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +0 -851
  326. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +0 -4932
  327. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +0 -1657
  328. scitex/fts/_fig/_editor/_gui/_flask_editor.py +0 -36
  329. scitex/fts/_fig/_models/_Annotations.py +0 -115
  330. scitex/fts/_fig/_models/_Axes.py +0 -152
  331. scitex/fts/_fig/_models/_Figure.py +0 -138
  332. scitex/fts/_fig/_models/_Plot.py +0 -123
  333. scitex/fts/_fig/_utils/_plot_layout.py +0 -397
  334. scitex/fts/_kinds/_figure/_composite.py +0 -345
  335. scitex/fts/_kinds/_plot/_backend/__init__.py +0 -53
  336. scitex/fts/_kinds/_plot/_backend/_export.py +0 -165
  337. scitex/fts/_kinds/_plot/_backend/_render.py +0 -538
  338. scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +0 -46
  339. scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +0 -82
  340. scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +0 -441
  341. scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +0 -52
  342. scitex/fts/_kinds/_plot/_dataclasses/__init__.py +0 -47
  343. scitex/fts/_kinds/_plot/_models/_Guides.py +0 -104
  344. scitex/fts/_kinds/_plot/_models/_Styles.py +0 -245
  345. scitex/fts/_kinds/_plot/_models/__init__.py +0 -80
  346. scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +0 -156
  347. scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +0 -43
  348. scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +0 -38
  349. scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +0 -36
  350. scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +0 -60
  351. scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +0 -30
  352. scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +0 -61
  353. scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +0 -57
  354. scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +0 -30
  355. scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +0 -121
  356. scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +0 -36
  357. scitex/fts/_kinds/_plot/_utils/__init__.py +0 -129
  358. scitex/fts/_kinds/_plot/_utils/_auto_layout.py +0 -127
  359. scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +0 -111
  360. scitex/fts/_kinds/_plot/_utils/_const_sizes.py +0 -48
  361. scitex/fts/_kinds/_plot/_utils/_convert_coords.py +0 -77
  362. scitex/fts/_kinds/_plot/_utils/_get_template.py +0 -178
  363. scitex/fts/_kinds/_plot/_utils/_normalize.py +0 -73
  364. scitex/fts/_kinds/_plot/_utils/_validate.py +0 -197
  365. scitex/fts/_kinds/_table/_latex/_export.py +0 -279
  366. scitex/fts/_stats/__init__.py +0 -48
  367. scitex/fts/_stats/_dataclasses/_Stats.py +0 -423
  368. scitex/fts/_stats/_dataclasses/__init__.py +0 -48
  369. scitex/fts/_tables/__init__.py +0 -65
  370. scitex/fts/_tables/_latex/__init__.py +0 -93
  371. scitex/fts/_tables/_latex/_editor/__init__.py +0 -11
  372. scitex/fts/_tables/_latex/_editor/_app.py +0 -725
  373. scitex/fts/_tables/_latex/_figure_exporter.py +0 -153
  374. scitex/fts/_tables/_latex/_stats_formatter.py +0 -274
  375. scitex/fts/_tables/_latex/_table_exporter.py +0 -362
  376. scitex/fts/_tables/_latex/_utils.py +0 -369
  377. scitex/fts/_tables/_latex/_validator.py +0 -445
  378. scitex/io/_save_modules/_pltz_bundle.py +0 -356
  379. scitex-2.10.3.dist-info/METADATA +0 -952
  380. scitex-2.10.3.dist-info/entry_points.txt +0 -2
  381. /scitex/{fig → canvas}/README.md +0 -0
  382. /scitex/{fig → canvas}/backend/__init__.py +0 -0
  383. /scitex/{fig → canvas}/backend/_export.py +0 -0
  384. /scitex/{fig → canvas}/backend/_render.py +0 -0
  385. /scitex/{fig → canvas}/docs/CANVAS_ARCHITECTURE.md +0 -0
  386. /scitex/{fig → canvas}/editor/__init__.py +0 -0
  387. /scitex/{fig → canvas}/editor/_dearpygui_editor.py +0 -0
  388. /scitex/{fig → canvas}/editor/_flask_editor.py +0 -0
  389. /scitex/{fig → canvas}/editor/_mpl_editor.py +0 -0
  390. /scitex/{fig → canvas}/editor/_qt_editor.py +0 -0
  391. /scitex/{fig → canvas}/editor/_tkinter_editor.py +0 -0
  392. /scitex/{fig → canvas}/editor/edit/backend_detector.py +0 -0
  393. /scitex/{fig → canvas}/editor/edit/manual_handler.py +0 -0
  394. /scitex/{fig → canvas}/editor/edit/path_resolver.py +0 -0
  395. /scitex/{fig → canvas}/editor/flask_editor/__init__.py +0 -0
  396. /scitex/{fig → canvas}/editor/flask_editor/_plotter.py +0 -0
  397. /scitex/{fig → canvas}/editor/flask_editor/_utils.py +0 -0
  398. /scitex/{fig → canvas}/editor/flask_editor/static/css/base/reset.css +0 -0
  399. /scitex/{fig → canvas}/editor/flask_editor/static/css/base/typography.css +0 -0
  400. /scitex/{fig → canvas}/editor/flask_editor/static/css/base/variables.css +0 -0
  401. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/buttons.css +0 -0
  402. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/context-menu.css +0 -0
  403. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/dropdown.css +0 -0
  404. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/forms.css +0 -0
  405. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/modal.css +0 -0
  406. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/sections.css +0 -0
  407. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/element-inspector.css +0 -0
  408. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/loading.css +0 -0
  409. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/overlay.css +0 -0
  410. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/selection.css +0 -0
  411. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/statistics.css +0 -0
  412. /scitex/{fig → canvas}/editor/flask_editor/static/css/index.css +0 -0
  413. /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/container.css +0 -0
  414. /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/controls.css +0 -0
  415. /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/preview.css +0 -0
  416. /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/axis.js +0 -0
  417. /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/basic.js +0 -0
  418. /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/distribute.js +0 -0
  419. /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/canvas.js +0 -0
  420. /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/dragging.js +0 -0
  421. /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/resize.js +0 -0
  422. /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/selection.js +0 -0
  423. /scitex/{fig → canvas}/editor/flask_editor/static/js/core/state.js +0 -0
  424. /scitex/{fig → canvas}/editor/flask_editor/static/js/core/utils.js +0 -0
  425. /scitex/{fig → canvas}/editor/flask_editor/static/js/dev/element-inspector.js +0 -0
  426. /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/bbox.js +0 -0
  427. /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/element-drag.js +0 -0
  428. /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/overlay.js +0 -0
  429. /scitex/{fig → canvas}/editor/flask_editor/static/js/main.js +0 -0
  430. /scitex/{fig → canvas}/editor/flask_editor/static/js/shortcuts/context-menu.js +0 -0
  431. /scitex/{fig → canvas}/editor/flask_editor/static/js/shortcuts/keyboard.js +0 -0
  432. /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/controls.js +0 -0
  433. /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/download.js +0 -0
  434. /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/help.js +0 -0
  435. /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/theme.js +0 -0
  436. /scitex/{fig → canvas}/editor/flask_editor/templates/__init__.py +0 -0
  437. /scitex/{fig → canvas}/io/_directory.py +0 -0
  438. /scitex/{fig → canvas}/model/_plot_types.py +0 -0
  439. /scitex/{fig → canvas}/utils/_defaults.py +0 -0
  440. /scitex/{fig → canvas}/utils/_validate.py +0 -0
  441. /scitex/{fts/_bundle → io/bundle}/_conversion/__init__.py +0 -0
  442. /scitex/{fts/_bundle → io/bundle}/_conversion/_bundle2dict.py +0 -0
  443. /scitex/{fts/_bundle → io/bundle}/_conversion/_dict2bundle.py +0 -0
  444. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_Axes.py +0 -0
  445. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_BBox.py +0 -0
  446. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_ColumnDef.py +0 -0
  447. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataFormat.py +0 -0
  448. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataInfo.py +0 -0
  449. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataSource.py +0 -0
  450. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_SizeMM.py +0 -0
  451. /scitex/{fts/_bundle → io/bundle}/_extractors/__init__.py +0 -0
  452. /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_bar.py +0 -0
  453. /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_line.py +0 -0
  454. /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_scatter.py +0 -0
  455. /scitex/{fts/_kinds → io/bundle/kinds}/__init__.py +0 -0
  456. /scitex/{fts/_kinds → io/bundle/kinds}/_figure/__init__.py +0 -0
  457. /scitex/{fts/_fig → io/bundle/kinds/_figure}/_composite.py +0 -0
  458. /scitex/{fts/_kinds → io/bundle/kinds}/_plot/__init__.py +0 -0
  459. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/__init__.py +0 -0
  460. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/_export.py +0 -0
  461. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/_render.py +0 -0
  462. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_ChannelEncoding.py +0 -0
  463. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_Encoding.py +0 -0
  464. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_Theme.py +0 -0
  465. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_TraceEncoding.py +0 -0
  466. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/__init__.py +0 -0
  467. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/__init__.py +0 -0
  468. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/__init__.py +0 -0
  469. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_bar.py +0 -0
  470. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_box.py +0 -0
  471. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_distribution.py +0 -0
  472. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_errorbar.py +0 -0
  473. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_histogram.py +0 -0
  474. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_image.py +0 -0
  475. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_line.py +0 -0
  476. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_scatter.py +0 -0
  477. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_seaborn.py +0 -0
  478. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_violin.py +0 -0
  479. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/__init__.py +0 -0
  480. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_auto_layout.py +0 -0
  481. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_calc_bounds.py +0 -0
  482. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_const_sizes.py +0 -0
  483. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_convert_coords.py +0 -0
  484. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_get_template.py +0 -0
  485. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_normalize.py +0 -0
  486. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_validate.py +0 -0
  487. /scitex/{fts/_kinds → io/bundle/kinds}/_shape/__init__.py +0 -0
  488. /scitex/{fts/_kinds → io/bundle/kinds}/_stats/__init__.py +0 -0
  489. /scitex/{fts/_kinds → io/bundle/kinds}/_stats/_dataclasses/_Stats.py +0 -0
  490. /scitex/{fts/_kinds → io/bundle/kinds}/_stats/_dataclasses/__init__.py +0 -0
  491. /scitex/{fts/_kinds → io/bundle/kinds}/_table/__init__.py +0 -0
  492. /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_editor/__init__.py +0 -0
  493. /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_stats_formatter.py +0 -0
  494. /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_utils.py +0 -0
  495. /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_validator.py +0 -0
  496. /scitex/{fts/_kinds → io/bundle/kinds}/_text/__init__.py +0 -0
  497. /scitex/{fts/_schemas → io/bundle/schemas}/data_info.schema.json +0 -0
  498. /scitex/{fts/_schemas → io/bundle/schemas}/encoding.schema.json +0 -0
  499. /scitex/{fts/_schemas → io/bundle/schemas}/node.schema.json +0 -0
  500. /scitex/{fts/_schemas → io/bundle/schemas}/render_manifest.schema.json +0 -0
  501. /scitex/{fts/_schemas → io/bundle/schemas}/stats.schema.json +0 -0
  502. /scitex/{fts/_schemas → io/bundle/schemas}/theme.schema.json +0 -0
  503. {scitex-2.10.3.dist-info → scitex-2.13.0.dist-info}/WHEEL +0 -0
  504. {scitex-2.10.3.dist-info → scitex-2.13.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,813 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- # File: ./src/scitex/vis/editor/flask_editor/renderer.py
4
- """Figure rendering for Flask editor - supports single and multi-axis figures."""
5
-
6
- from typing import Dict, Any, Tuple, Optional, List
7
- import base64
8
- import io
9
-
10
- import matplotlib
11
-
12
- matplotlib.use("Agg")
13
- import matplotlib.pyplot as plt
14
- from matplotlib.ticker import MaxNLocator
15
- from PIL import Image
16
- import numpy as np
17
-
18
- from ._plotter import plot_from_csv, plot_from_recipe
19
- from ._bbox import extract_bboxes, extract_bboxes_multi
20
- from scitex.plt.styles import get_default_dpi
21
-
22
- # mm to pt conversion factor
23
- MM_TO_PT = 2.83465
24
-
25
-
26
- def render_preview_with_bboxes(
27
- csv_data, overrides: Dict[str, Any], axis_fontsize: int = 7,
28
- metadata: Optional[Dict[str, Any]] = None,
29
- dark_mode: bool = False,
30
- ) -> Tuple[str, Dict[str, Any], Dict[str, int]]:
31
- """Render figure and return base64 PNG along with element bounding boxes.
32
-
33
- Args:
34
- csv_data: DataFrame containing CSV data
35
- overrides: Dictionary with override settings
36
- axis_fontsize: Default font size for axis labels
37
- metadata: Optional JSON metadata (new schema with axes dict)
38
- dark_mode: Whether to render with dark mode colors (light text/spines)
39
-
40
- Returns:
41
- tuple: (base64_image_data, bboxes_dict, image_size)
42
- """
43
- # Check if this is a multi-axis figure (new schema)
44
- if metadata and "axes" in metadata and isinstance(metadata.get("axes"), dict):
45
- return render_multi_axis_preview(csv_data, overrides, metadata, dark_mode)
46
-
47
- # Fall back to single-axis rendering
48
- return render_single_axis_preview(csv_data, overrides, axis_fontsize, dark_mode)
49
-
50
-
51
- def render_single_axis_preview(
52
- csv_data, overrides: Dict[str, Any], axis_fontsize: int = 7,
53
- dark_mode: bool = False,
54
- ) -> Tuple[str, Dict[str, Any], Dict[str, int]]:
55
- """Render single-axis figure (legacy mode)."""
56
- o = overrides
57
-
58
- # Dimensions
59
- dpi = o.get("dpi", get_default_dpi())
60
- fig_size = o.get("fig_size", [3.15, 2.68])
61
-
62
- # Font sizes
63
- axis_fontsize = o.get("axis_fontsize", 7)
64
- tick_fontsize = o.get("tick_fontsize", 7)
65
- title_fontsize = o.get("title_fontsize", 8)
66
-
67
- # Line/axis thickness
68
- linewidth_pt = o.get("linewidth", 0.57)
69
- axis_width_pt = o.get("axis_width", 0.2) * MM_TO_PT
70
- tick_length_pt = o.get("tick_length", 0.8) * MM_TO_PT
71
- tick_width_pt = o.get("tick_width", 0.2) * MM_TO_PT
72
- tick_direction = o.get("tick_direction", "out")
73
- x_n_ticks = o.get("x_n_ticks", o.get("n_ticks", 4))
74
- y_n_ticks = o.get("y_n_ticks", o.get("n_ticks", 4))
75
- hide_x_ticks = o.get("hide_x_ticks", False)
76
- hide_y_ticks = o.get("hide_y_ticks", False)
77
-
78
- transparent = o.get("transparent", True)
79
-
80
- # Create figure
81
- fig, ax = plt.subplots(figsize=fig_size, dpi=dpi)
82
- _apply_background(fig, ax, o, transparent)
83
-
84
- # Plot from CSV data
85
- if csv_data is not None:
86
- plot_from_csv(ax, csv_data, overrides, linewidth=linewidth_pt)
87
- else:
88
- ax.text(
89
- 0.5,
90
- 0.5,
91
- "No plot data available\n(CSV not found)",
92
- ha="center",
93
- va="center",
94
- transform=ax.transAxes,
95
- fontsize=axis_fontsize,
96
- )
97
-
98
- # Apply labels
99
- _apply_labels(ax, o, title_fontsize, axis_fontsize)
100
-
101
- # Tick styling
102
- _apply_tick_styling(
103
- ax,
104
- tick_fontsize,
105
- tick_length_pt,
106
- tick_width_pt,
107
- tick_direction,
108
- x_n_ticks,
109
- y_n_ticks,
110
- hide_x_ticks,
111
- hide_y_ticks,
112
- )
113
-
114
- # Apply grid, limits, spines
115
- _apply_style(ax, o, axis_width_pt)
116
-
117
- # Apply annotations
118
- _apply_annotations(ax, o, axis_fontsize)
119
-
120
- # Apply caption (below figure)
121
- caption_artist = _apply_caption(fig, o)
122
-
123
- # Apply dark mode styling if requested
124
- if dark_mode:
125
- _apply_dark_theme(ax)
126
- # Also style caption if present
127
- if caption_artist:
128
- caption_artist.set_color(DARK_THEME_TEXT_COLOR)
129
-
130
- fig.tight_layout()
131
-
132
- # Get element bounding boxes BEFORE saving (need renderer)
133
- fig.canvas.draw()
134
- renderer = fig.canvas.get_renderer()
135
-
136
- # Save to buffer first to get actual image size
137
- buf = io.BytesIO()
138
- fig.savefig(
139
- buf, format="png", dpi=dpi, bbox_inches="tight", transparent=transparent
140
- )
141
- buf.seek(0)
142
-
143
- # Get actual saved image dimensions
144
- img = Image.open(buf)
145
- img_width, img_height = img.size
146
- buf.seek(0)
147
-
148
- # Get bboxes
149
- bboxes = extract_bboxes(fig, ax, renderer, img_width, img_height)
150
-
151
- img_data = base64.b64encode(buf.read()).decode("utf-8")
152
- plt.close(fig)
153
-
154
- return img_data, bboxes, {"width": img_width, "height": img_height}
155
-
156
-
157
- def render_multi_axis_preview(
158
- csv_data, overrides: Dict[str, Any], metadata: Dict[str, Any],
159
- dark_mode: bool = False,
160
- ) -> Tuple[str, Dict[str, Any], Dict[str, int]]:
161
- """Render multi-axis figure from new schema (scitex.plt.figure.recipe).
162
-
163
- Args:
164
- csv_data: DataFrame containing CSV data
165
- overrides: Dictionary with override settings
166
- metadata: JSON metadata with axes dict
167
- dark_mode: Whether to render with dark mode colors
168
-
169
- Returns:
170
- tuple: (base64_image_data, bboxes_dict, image_size)
171
- """
172
- o = overrides
173
- axes_spec = metadata.get("axes", {})
174
- fig_spec = metadata.get("figure", {})
175
-
176
- # Get grid dimensions from axes positions
177
- nrows, ncols = _get_grid_dimensions(axes_spec)
178
-
179
- # Figure dimensions
180
- dpi = fig_spec.get("dpi", o.get("dpi", get_default_dpi()))
181
- size_mm = fig_spec.get("size_mm", [176, 106])
182
- # Convert mm to inches (1 inch = 25.4 mm)
183
- fig_size = (size_mm[0] / 25.4, size_mm[1] / 25.4)
184
-
185
- # Font sizes (from overrides)
186
- axis_fontsize = o.get("axis_fontsize", 7)
187
- tick_fontsize = o.get("tick_fontsize", 7)
188
- title_fontsize = o.get("title_fontsize", 8)
189
-
190
- # Line/axis thickness
191
- linewidth_pt = o.get("linewidth", 0.57)
192
- axis_width_pt = o.get("axis_width", 0.2) * MM_TO_PT
193
- tick_length_pt = o.get("tick_length", 0.8) * MM_TO_PT
194
- tick_width_pt = o.get("tick_width", 0.2) * MM_TO_PT
195
- tick_direction = o.get("tick_direction", "out")
196
- x_n_ticks = o.get("x_n_ticks", o.get("n_ticks", 4))
197
- y_n_ticks = o.get("y_n_ticks", o.get("n_ticks", 4))
198
-
199
- transparent = o.get("transparent", True)
200
-
201
- # Create multi-axis figure
202
- fig, axes_array = plt.subplots(nrows, ncols, figsize=fig_size, dpi=dpi)
203
-
204
- # Handle 1D or 2D array
205
- if nrows == 1 and ncols == 1:
206
- axes_array = np.array([[axes_array]])
207
- elif nrows == 1:
208
- axes_array = axes_array.reshape(1, -1)
209
- elif ncols == 1:
210
- axes_array = axes_array.reshape(-1, 1)
211
-
212
- # Apply background to figure
213
- if transparent:
214
- fig.patch.set_facecolor("none")
215
- elif o.get("facecolor"):
216
- fig.patch.set_facecolor(o["facecolor"])
217
-
218
- # Build mapping from axis ID to row/col
219
- ax_to_rowcol = _build_ax_to_rowcol_map(axes_spec)
220
-
221
- # Map axes by their ID
222
- axes_map = {}
223
- for ax_id, ax_spec in axes_spec.items():
224
- row, col = ax_to_rowcol.get(ax_id, (0, 0))
225
- ax = axes_array[row, col]
226
- axes_map[ax_id] = ax
227
-
228
- # Apply background
229
- if transparent:
230
- ax.patch.set_facecolor("none")
231
- elif o.get("facecolor"):
232
- ax.patch.set_facecolor(o["facecolor"])
233
-
234
- # Plot data - check which schema format
235
- if csv_data is not None:
236
- calls = ax_spec.get("calls", [])
237
- if calls:
238
- # Recipe schema with explicit calls
239
- plot_from_recipe(ax, csv_data, ax_spec, overrides, linewidth_pt, ax_id=ax_id)
240
- else:
241
- # Editable schema - plot from CSV column names
242
- csv_row, csv_col = row, col # Use computed row/col for CSV lookup
243
- _plot_from_editable_csv(ax, csv_data, ax_id, csv_row, csv_col, overrides, linewidth_pt, metadata)
244
-
245
- # Get panel-specific overrides (e.g., ax_00_panel)
246
- panel_key = f"{ax_id}_panel"
247
- element_overrides = o.get("element_overrides", {})
248
- panel_overrides = element_overrides.get(panel_key, {})
249
-
250
- # Apply axis labels from spec, with panel overrides taking precedence
251
- xaxis = ax_spec.get("xaxis", {})
252
- yaxis = ax_spec.get("yaxis", {})
253
-
254
- # Panel title (from overrides or spec)
255
- panel_title = panel_overrides.get("title")
256
- if panel_title:
257
- ax.set_title(panel_title, fontsize=title_fontsize)
258
-
259
- # X/Y labels (panel overrides take precedence over spec)
260
- xlabel = panel_overrides.get("xlabel") or xaxis.get("label")
261
- ylabel = panel_overrides.get("ylabel") or yaxis.get("label")
262
-
263
- if xlabel:
264
- ax.set_xlabel(xlabel, fontsize=axis_fontsize)
265
- if ylabel:
266
- ax.set_ylabel(ylabel, fontsize=axis_fontsize)
267
-
268
- # Apply axis limits
269
- if xaxis.get("lim"):
270
- ax.set_xlim(xaxis["lim"])
271
- if yaxis.get("lim"):
272
- ax.set_ylim(yaxis["lim"])
273
-
274
- # Tick styling
275
- _apply_tick_styling(
276
- ax,
277
- tick_fontsize,
278
- tick_length_pt,
279
- tick_width_pt,
280
- tick_direction,
281
- x_n_ticks,
282
- y_n_ticks,
283
- False, # hide_x_ticks
284
- False, # hide_y_ticks
285
- )
286
-
287
- # Apply spines
288
- if o.get("hide_top_spine", True):
289
- ax.spines["top"].set_visible(False)
290
- if o.get("hide_right_spine", True):
291
- ax.spines["right"].set_visible(False)
292
- for spine in ax.spines.values():
293
- spine.set_linewidth(axis_width_pt)
294
-
295
- # Apply dark mode to this axis
296
- if dark_mode:
297
- _apply_dark_theme(ax)
298
-
299
- # Apply caption (below figure) - use global overrides
300
- caption_artist = _apply_caption(fig, o)
301
- if dark_mode and caption_artist:
302
- caption_artist.set_color(DARK_THEME_TEXT_COLOR)
303
-
304
- fig.tight_layout()
305
-
306
- # Get element bounding boxes
307
- fig.canvas.draw()
308
- renderer = fig.canvas.get_renderer()
309
-
310
- # Save to buffer
311
- buf = io.BytesIO()
312
- fig.savefig(
313
- buf, format="png", dpi=dpi, bbox_inches="tight", transparent=transparent
314
- )
315
- buf.seek(0)
316
-
317
- # Get actual saved image dimensions
318
- img = Image.open(buf)
319
- img_width, img_height = img.size
320
- buf.seek(0)
321
-
322
- # Get bboxes for all axes
323
- bboxes = extract_bboxes_multi(fig, axes_map, renderer, img_width, img_height)
324
-
325
- img_data = base64.b64encode(buf.read()).decode("utf-8")
326
- plt.close(fig)
327
-
328
- return img_data, bboxes, {"width": img_width, "height": img_height}
329
-
330
-
331
- def _get_grid_dimensions(axes_spec: Dict[str, Any]) -> Tuple[int, int]:
332
- """Get grid dimensions from axes specifications.
333
-
334
- Handles both:
335
- - New recipe schema with grid_position
336
- - Editable schema - infer grid from positions (y-position groups = rows)
337
- """
338
- # Check if any axis has grid_position
339
- has_grid_pos = any(ax_spec.get("grid_position") for ax_spec in axes_spec.values())
340
-
341
- if has_grid_pos:
342
- max_row = 0
343
- max_col = 0
344
- for ax_id, ax_spec in axes_spec.items():
345
- pos = ax_spec.get("grid_position", {})
346
- max_row = max(max_row, pos.get("row", 0))
347
- max_col = max(max_col, pos.get("col", 0))
348
- return max_row + 1, max_col + 1
349
-
350
- # Editable schema: infer grid from positions
351
- # Group by y-position to determine rows
352
- if not axes_spec:
353
- return 1, 1
354
-
355
- positions = []
356
- for ax_id, ax_spec in axes_spec.items():
357
- pos = ax_spec.get("position", [0, 0, 0, 0])
358
- if len(pos) >= 2:
359
- positions.append((ax_id, pos[0], pos[1])) # (id, x, y)
360
-
361
- if not positions:
362
- return 1, 1
363
-
364
- # Cluster by y-position (tolerance for floating point)
365
- y_values = sorted(set(round(p[2], 2) for p in positions), reverse=True)
366
- n_rows = len(y_values)
367
-
368
- # Count columns in first row
369
- first_row_y = y_values[0]
370
- n_cols = sum(1 for p in positions if abs(p[2] - first_row_y) < 0.1)
371
-
372
- return n_rows, n_cols
373
-
374
-
375
- def _build_ax_to_rowcol_map(axes_spec: Dict[str, Any]) -> Dict[str, Tuple[int, int]]:
376
- """Build mapping from axis ID to (row, col) based on positions."""
377
- if not axes_spec:
378
- return {}
379
-
380
- # Check if any axis has grid_position
381
- has_grid_pos = any(ax_spec.get("grid_position") for ax_spec in axes_spec.values())
382
-
383
- if has_grid_pos:
384
- result = {}
385
- for ax_id, ax_spec in axes_spec.items():
386
- pos = ax_spec.get("grid_position", {})
387
- result[ax_id] = (pos.get("row", 0), pos.get("col", 0))
388
- return result
389
-
390
- # Editable schema: compute from positions
391
- positions = []
392
- for ax_id, ax_spec in axes_spec.items():
393
- pos = ax_spec.get("position", [0, 0, 0, 0])
394
- if len(pos) >= 2:
395
- positions.append((ax_id, pos[0], pos[1])) # (id, x, y)
396
-
397
- if not positions:
398
- return {ax_id: (0, 0) for ax_id in axes_spec}
399
-
400
- # Get unique y-values (rows) - higher y = lower row number (top of figure)
401
- y_values = sorted(set(round(p[2], 2) for p in positions), reverse=True)
402
- y_to_row = {y: i for i, y in enumerate(y_values)}
403
-
404
- # For each row, sort by x to get column
405
- result = {}
406
- for row_idx, row_y in enumerate(y_values):
407
- row_axes = [(ax_id, x) for ax_id, x, y in positions if abs(y - row_y) < 0.1]
408
- row_axes.sort(key=lambda t: t[1]) # Sort by x
409
- for col_idx, (ax_id, _) in enumerate(row_axes):
410
- result[ax_id] = (row_idx, col_idx)
411
-
412
- return result
413
-
414
-
415
- def _get_row_col_from_ax_id(ax_id: str, ax_map: Optional[Dict[str, Tuple[int, int]]] = None) -> Tuple[int, int]:
416
- """Extract row and col from axis ID using the mapping."""
417
- if ax_map and ax_id in ax_map:
418
- return ax_map[ax_id]
419
- # Fallback: try parsing ax_XY format
420
- import re
421
- match = re.match(r'ax_(\d)(\d)', ax_id)
422
- if match:
423
- return int(match.group(1)), int(match.group(2))
424
- return 0, 0
425
-
426
-
427
- def _plot_from_editable_csv(
428
- ax, csv_data, ax_id: str, row: int, col: int,
429
- overrides: Dict[str, Any], linewidth: float,
430
- metadata: Optional[Dict[str, Any]] = None,
431
- ):
432
- """Plot data from editable schema CSV format.
433
-
434
- CSV columns follow pattern: ax-row-X-col-Y_trace-id-NAME_variable-VAR
435
- """
436
- import re
437
- df = csv_data
438
- elements = metadata.get("elements", {}) if metadata else {}
439
-
440
- # Find columns for this axis (by row/col)
441
- pattern = f"ax-row-{row}-col-{col}_"
442
- ax_cols = [c for c in df.columns if c.startswith(pattern)]
443
-
444
- if not ax_cols:
445
- return
446
-
447
- # Group columns by trace-id
448
- traces = {}
449
- for col_name in ax_cols:
450
- # Parse: ax-row-X-col-Y_trace-id-NAME_variable-VAR
451
- match = re.match(rf'{pattern}trace-id-(.+?)_variable-(.+)', col_name)
452
- if match:
453
- trace_id = match.group(1)
454
- var_name = match.group(2)
455
- if trace_id not in traces:
456
- traces[trace_id] = {}
457
- traces[trace_id][var_name] = col_name
458
-
459
- # Get element overrides from overrides dict
460
- element_overrides = overrides.get("element_overrides", {})
461
-
462
- # Plot each trace
463
- trace_idx = 0
464
- for trace_id, vars_dict in traces.items():
465
- # Find element info from metadata
466
- element_key = f"{ax_id}_line_{trace_idx:02d}"
467
- element_info = elements.get(element_key, {})
468
- label = element_info.get("label", trace_id)
469
-
470
- # Get user overrides for this trace
471
- override_key = f"{ax_id}_trace_{trace_idx}"
472
- trace_overrides = element_overrides.get(override_key, {})
473
-
474
- # Determine plot type based on variables present
475
- # Check more specific patterns first, then fall back to x/y
476
-
477
- if 'y_lower' in vars_dict and 'y_middle' in vars_dict and 'y_upper' in vars_dict:
478
- # Shaded line plot (fill_between + line)
479
- x_col = vars_dict.get('x')
480
- if x_col:
481
- x = df[x_col].dropna().values
482
- y_lower = df[vars_dict['y_lower']].dropna().values
483
- y_middle = df[vars_dict['y_middle']].dropna().values
484
- y_upper = df[vars_dict['y_upper']].dropna().values
485
-
486
- min_len = min(len(x), len(y_lower), len(y_middle), len(y_upper))
487
- if min_len > 0:
488
- ax.fill_between(x[:min_len], y_lower[:min_len], y_upper[:min_len], alpha=0.3)
489
- ax.plot(x[:min_len], y_middle[:min_len], linewidth=linewidth)
490
- trace_idx += 1
491
-
492
- elif 'row' in vars_dict and 'col' in vars_dict and 'value' in vars_dict:
493
- # Heatmap / imshow
494
- rows_data = df[vars_dict['row']].dropna().values
495
- cols_data = df[vars_dict['col']].dropna().values
496
- values = df[vars_dict['value']].dropna().values
497
- if len(rows_data) > 0:
498
- n_rows = int(rows_data.max()) + 1
499
- n_cols = int(cols_data.max()) + 1
500
- data = np.zeros((n_rows, n_cols))
501
- for r, c, v in zip(rows_data.astype(int), cols_data.astype(int), values):
502
- data[r, c] = v
503
- ax.imshow(data, aspect='auto', origin='lower')
504
-
505
- elif 'y1' in vars_dict and 'y2' in vars_dict:
506
- # fill_between (CI band)
507
- x_col = vars_dict.get('x')
508
- if x_col:
509
- x = df[x_col].dropna().values
510
- y1 = df[vars_dict['y1']].dropna().values
511
- y2 = df[vars_dict['y2']].dropna().values
512
- min_len = min(len(x), len(y1), len(y2))
513
- if min_len > 0:
514
- ax.fill_between(x[:min_len], y1[:min_len], y2[:min_len], alpha=0.3)
515
-
516
- elif 'yerr' in vars_dict and 'y' in vars_dict:
517
- # Error bars with bar chart
518
- x_col = vars_dict.get('x')
519
- y_col = vars_dict.get('y')
520
- if x_col and y_col:
521
- x = df[x_col].dropna().values
522
- y = df[y_col].dropna().values
523
- yerr = df[vars_dict['yerr']].dropna().values
524
- min_len = min(len(x), len(y), len(yerr))
525
- if min_len > 0:
526
- ax.bar(x[:min_len], y[:min_len], yerr=yerr[:min_len])
527
-
528
- elif 'group' in vars_dict and 'value' in vars_dict:
529
- # Violin/strip plot - plot as scatter for now
530
- groups = df[vars_dict['group']].dropna().values
531
- values = df[vars_dict['value']].dropna().values
532
- if len(groups) > 0 and len(values) > 0:
533
- min_len = min(len(groups), len(values))
534
- # Convert string groups to numeric positions
535
- unique_groups = list(dict.fromkeys(groups[:min_len])) # Preserve order
536
- group_to_x = {g: i for i, g in enumerate(unique_groups)}
537
- x_positions = np.array([group_to_x.get(g, 0) for g in groups[:min_len]])
538
- # Add jitter for strip plot effect
539
- jitter = np.random.uniform(-0.1, 0.1, min_len)
540
- ax.scatter(x_positions + jitter, values[:min_len], alpha=0.6, s=20)
541
- # Set tick labels
542
- ax.set_xticks(range(len(unique_groups)))
543
- ax.set_xticklabels(unique_groups, fontsize=6)
544
-
545
- elif 'width' in vars_dict and 'height' in vars_dict:
546
- # Rectangle - skip for now
547
- pass
548
-
549
- elif 'type' in vars_dict:
550
- # Skip type-only entries (like stim markers)
551
- pass
552
-
553
- elif 'content' in vars_dict:
554
- # Text annotation - skip for preview
555
- pass
556
-
557
- elif 'x' in vars_dict and 'y' in vars_dict:
558
- # Default: line or scatter plot
559
- x_col = vars_dict['x']
560
- y_col = vars_dict['y']
561
- x = df[x_col].dropna().values
562
- y = df[y_col].dropna().values
563
- if len(x) > 0 and len(y) > 0:
564
- min_len = min(len(x), len(y))
565
- # Apply overrides
566
- color = trace_overrides.get("color")
567
- lw = trace_overrides.get("linewidth", linewidth)
568
- ls = trace_overrides.get("linestyle", "-")
569
- marker = trace_overrides.get("marker")
570
- alpha = trace_overrides.get("alpha", 1.0)
571
-
572
- kwargs = {"linewidth": lw, "linestyle": ls, "alpha": alpha}
573
- if color:
574
- kwargs["color"] = color
575
- if marker:
576
- kwargs["marker"] = marker
577
- if label and label != trace_id:
578
- kwargs["label"] = label
579
-
580
- # Check if this looks like scatter data (trace-id contains 'scatter' or 'strip')
581
- if 'scatter' in trace_id.lower() or 'strip' in trace_id.lower():
582
- scatter_kwargs = {"alpha": alpha, "s": 20}
583
- if color:
584
- scatter_kwargs["c"] = color
585
- ax.scatter(x[:min_len], y[:min_len], **scatter_kwargs)
586
- else:
587
- ax.plot(x[:min_len], y[:min_len], **kwargs)
588
- trace_idx += 1
589
-
590
-
591
- def _apply_background(fig, ax, o, transparent):
592
- """Apply background settings to figure."""
593
- if transparent:
594
- fig.patch.set_facecolor("none")
595
- ax.patch.set_facecolor("none")
596
- elif o.get("facecolor"):
597
- fig.patch.set_facecolor(o["facecolor"])
598
- ax.patch.set_facecolor(o["facecolor"])
599
-
600
-
601
- def _apply_labels(ax, o, title_fontsize, axis_fontsize):
602
- """Apply title and axis labels."""
603
- # Show title only if enabled (default True)
604
- if o.get("show_title", True) and o.get("title"):
605
- ax.set_title(o["title"], fontsize=title_fontsize)
606
- if o.get("xlabel"):
607
- ax.set_xlabel(o["xlabel"], fontsize=axis_fontsize)
608
- if o.get("ylabel"):
609
- ax.set_ylabel(o["ylabel"], fontsize=axis_fontsize)
610
-
611
-
612
- def _apply_caption(fig, o, caption_fontsize=7):
613
- """Apply caption below the figure."""
614
- if not o.get("show_caption", False) or not o.get("caption"):
615
- return None
616
-
617
- caption_text = o.get("caption", "")
618
- fontsize = o.get("caption_fontsize", caption_fontsize)
619
-
620
- # Place caption below the figure
621
- # Using fig.text with y position slightly below 0
622
- caption_artist = fig.text(
623
- 0.5, -0.02, # Centered, below the figure
624
- caption_text,
625
- ha="center",
626
- va="top",
627
- fontsize=fontsize,
628
- wrap=True,
629
- transform=fig.transFigure,
630
- )
631
- return caption_artist
632
-
633
-
634
- def _apply_tick_styling(
635
- ax,
636
- tick_fontsize,
637
- tick_length_pt,
638
- tick_width_pt,
639
- tick_direction,
640
- x_n_ticks,
641
- y_n_ticks,
642
- hide_x_ticks,
643
- hide_y_ticks,
644
- ):
645
- """Apply tick styling to axes."""
646
- ax.tick_params(
647
- axis="both",
648
- labelsize=tick_fontsize,
649
- length=tick_length_pt,
650
- width=tick_width_pt,
651
- direction=tick_direction,
652
- )
653
-
654
- if hide_x_ticks:
655
- ax.xaxis.set_ticks([])
656
- ax.xaxis.set_ticklabels([])
657
- else:
658
- ax.xaxis.set_major_locator(MaxNLocator(nbins=x_n_ticks))
659
- if hide_y_ticks:
660
- ax.yaxis.set_ticks([])
661
- ax.yaxis.set_ticklabels([])
662
- else:
663
- ax.yaxis.set_major_locator(MaxNLocator(nbins=y_n_ticks))
664
-
665
-
666
- def _apply_style(ax, o, axis_width_pt):
667
- """Apply grid, axis limits, and spine settings."""
668
- if o.get("grid"):
669
- ax.grid(True, linewidth=axis_width_pt, alpha=0.3)
670
-
671
- if o.get("xlim"):
672
- ax.set_xlim(o["xlim"])
673
- if o.get("ylim"):
674
- ax.set_ylim(o["ylim"])
675
-
676
- if o.get("hide_top_spine", True):
677
- ax.spines["top"].set_visible(False)
678
- if o.get("hide_right_spine", True):
679
- ax.spines["right"].set_visible(False)
680
-
681
- for spine in ax.spines.values():
682
- spine.set_linewidth(axis_width_pt)
683
-
684
-
685
- def _apply_annotations(ax, o, axis_fontsize):
686
- """Apply text annotations to figure."""
687
- for annot in o.get("annotations", []):
688
- if annot.get("type") == "text":
689
- ax.text(
690
- annot.get("x", 0.5),
691
- annot.get("y", 0.5),
692
- annot.get("text", ""),
693
- transform=ax.transAxes,
694
- fontsize=annot.get("fontsize", axis_fontsize),
695
- )
696
-
697
-
698
- def render_panel_preview(
699
- panel_dir,
700
- dark_mode: bool = False,
701
- ) -> Tuple[Optional[str], Optional[Dict[str, Any]], Optional[Dict[str, int]]]:
702
- """Render a panel from its pltz bundle directory with dark mode support.
703
-
704
- Args:
705
- panel_dir: Path to the .pltz.d panel directory
706
- dark_mode: Whether to render with dark mode colors
707
-
708
- Returns:
709
- tuple: (base64_image_data, bboxes_dict, image_size) or (None, None, None) on error
710
- """
711
- from pathlib import Path
712
- import json
713
- import pandas as pd
714
-
715
- panel_dir = Path(panel_dir)
716
-
717
- try:
718
- # Load spec.json
719
- spec_path = panel_dir / "spec.json"
720
- if not spec_path.exists():
721
- # Try legacy format
722
- for f in panel_dir.glob("*.json"):
723
- if f.name != "style.json":
724
- spec_path = f
725
- break
726
-
727
- if not spec_path.exists():
728
- return None, None, None
729
-
730
- with open(spec_path, "r") as f:
731
- metadata = json.load(f)
732
-
733
- # Load CSV data
734
- csv_data = None
735
- csv_path = panel_dir / "data.csv"
736
- if not csv_path.exists():
737
- for f in panel_dir.glob("*.csv"):
738
- csv_path = f
739
- break
740
-
741
- if csv_path.exists():
742
- csv_data = pd.read_csv(csv_path)
743
-
744
- # Load style.json for overrides
745
- style_path = panel_dir / "style.json"
746
- overrides = {}
747
- if style_path.exists():
748
- with open(style_path, "r") as f:
749
- style = json.load(f)
750
- # Convert style to overrides format
751
- size = style.get("size", {})
752
- if size:
753
- width_mm = size.get("width_mm", 80)
754
- height_mm = size.get("height_mm", 68)
755
- overrides["fig_size"] = [width_mm / 25.4, height_mm / 25.4]
756
- overrides["transparent"] = True
757
-
758
- # Render with dark mode
759
- return render_preview_with_bboxes(
760
- csv_data, overrides,
761
- metadata=metadata,
762
- dark_mode=dark_mode,
763
- )
764
-
765
- except Exception as e:
766
- import traceback
767
- print(f"Error rendering panel {panel_dir}: {e}")
768
- traceback.print_exc()
769
- return None, None, None
770
-
771
-
772
- # Dark mode theme colors
773
- DARK_THEME_TEXT_COLOR = "#e8e8e8" # Light gray for visibility on dark background
774
- DARK_THEME_SPINE_COLOR = "#e8e8e8"
775
- DARK_THEME_TICK_COLOR = "#e8e8e8"
776
-
777
-
778
- def _apply_dark_theme(ax):
779
- """Apply dark mode colors to axes for visibility on dark backgrounds.
780
-
781
- Changes title, labels, tick labels, spines, and legend text to light colors.
782
- """
783
- # Title
784
- title = ax.get_title()
785
- if title:
786
- ax.title.set_color(DARK_THEME_TEXT_COLOR)
787
-
788
- # Axis labels
789
- ax.xaxis.label.set_color(DARK_THEME_TEXT_COLOR)
790
- ax.yaxis.label.set_color(DARK_THEME_TEXT_COLOR)
791
-
792
- # Tick labels
793
- ax.tick_params(axis="both", colors=DARK_THEME_TICK_COLOR, labelcolor=DARK_THEME_TEXT_COLOR)
794
-
795
- # Spines
796
- for spine in ax.spines.values():
797
- spine.set_color(DARK_THEME_SPINE_COLOR)
798
-
799
- # Legend (if exists)
800
- legend = ax.get_legend()
801
- if legend:
802
- for text in legend.get_texts():
803
- text.set_color(DARK_THEME_TEXT_COLOR)
804
- # Legend title
805
- legend_title = legend.get_title()
806
- if legend_title:
807
- legend_title.set_color(DARK_THEME_TEXT_COLOR)
808
- # Legend frame (make transparent or dark)
809
- legend.get_frame().set_facecolor("none")
810
- legend.get_frame().set_edgecolor(DARK_THEME_SPINE_COLOR)
811
-
812
-
813
- # EOF