scitex 2.7.0__py3-none-any.whl → 2.8.1__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 (355) hide show
  1. scitex/__init__.py +6 -2
  2. scitex/__version__.py +1 -1
  3. scitex/audio/README.md +52 -0
  4. scitex/audio/__init__.py +384 -0
  5. scitex/audio/__main__.py +129 -0
  6. scitex/audio/_tts.py +334 -0
  7. scitex/audio/engines/__init__.py +44 -0
  8. scitex/audio/engines/base.py +275 -0
  9. scitex/audio/engines/elevenlabs_engine.py +143 -0
  10. scitex/audio/engines/gtts_engine.py +162 -0
  11. scitex/audio/engines/pyttsx3_engine.py +131 -0
  12. scitex/audio/mcp_server.py +757 -0
  13. scitex/bridge/_helpers.py +1 -1
  14. scitex/bridge/_plt_vis.py +1 -1
  15. scitex/bridge/_stats_vis.py +1 -1
  16. scitex/dev/plt/__init__.py +272 -0
  17. scitex/dev/plt/plot_mpl_axhline.py +28 -0
  18. scitex/dev/plt/plot_mpl_axhspan.py +28 -0
  19. scitex/dev/plt/plot_mpl_axvline.py +28 -0
  20. scitex/dev/plt/plot_mpl_axvspan.py +28 -0
  21. scitex/dev/plt/plot_mpl_bar.py +29 -0
  22. scitex/dev/plt/plot_mpl_barh.py +29 -0
  23. scitex/dev/plt/plot_mpl_boxplot.py +28 -0
  24. scitex/dev/plt/plot_mpl_contour.py +31 -0
  25. scitex/dev/plt/plot_mpl_contourf.py +31 -0
  26. scitex/dev/plt/plot_mpl_errorbar.py +30 -0
  27. scitex/dev/plt/plot_mpl_eventplot.py +28 -0
  28. scitex/dev/plt/plot_mpl_fill.py +30 -0
  29. scitex/dev/plt/plot_mpl_fill_between.py +31 -0
  30. scitex/dev/plt/plot_mpl_hexbin.py +28 -0
  31. scitex/dev/plt/plot_mpl_hist.py +28 -0
  32. scitex/dev/plt/plot_mpl_hist2d.py +28 -0
  33. scitex/dev/plt/plot_mpl_imshow.py +29 -0
  34. scitex/dev/plt/plot_mpl_pcolormesh.py +31 -0
  35. scitex/dev/plt/plot_mpl_pie.py +29 -0
  36. scitex/dev/plt/plot_mpl_plot.py +29 -0
  37. scitex/dev/plt/plot_mpl_quiver.py +31 -0
  38. scitex/dev/plt/plot_mpl_scatter.py +28 -0
  39. scitex/dev/plt/plot_mpl_stackplot.py +31 -0
  40. scitex/dev/plt/plot_mpl_stem.py +29 -0
  41. scitex/dev/plt/plot_mpl_step.py +29 -0
  42. scitex/dev/plt/plot_mpl_violinplot.py +28 -0
  43. scitex/dev/plt/plot_sns_barplot.py +29 -0
  44. scitex/dev/plt/plot_sns_boxplot.py +29 -0
  45. scitex/dev/plt/plot_sns_heatmap.py +28 -0
  46. scitex/dev/plt/plot_sns_histplot.py +29 -0
  47. scitex/dev/plt/plot_sns_kdeplot.py +29 -0
  48. scitex/dev/plt/plot_sns_lineplot.py +31 -0
  49. scitex/dev/plt/plot_sns_scatterplot.py +29 -0
  50. scitex/dev/plt/plot_sns_stripplot.py +29 -0
  51. scitex/dev/plt/plot_sns_swarmplot.py +29 -0
  52. scitex/dev/plt/plot_sns_violinplot.py +29 -0
  53. scitex/dev/plt/plot_stx_bar.py +29 -0
  54. scitex/dev/plt/plot_stx_barh.py +29 -0
  55. scitex/dev/plt/plot_stx_box.py +28 -0
  56. scitex/dev/plt/plot_stx_boxplot.py +28 -0
  57. scitex/dev/plt/plot_stx_conf_mat.py +28 -0
  58. scitex/dev/plt/plot_stx_contour.py +31 -0
  59. scitex/dev/plt/plot_stx_ecdf.py +28 -0
  60. scitex/dev/plt/plot_stx_errorbar.py +30 -0
  61. scitex/dev/plt/plot_stx_fill_between.py +31 -0
  62. scitex/dev/plt/plot_stx_fillv.py +28 -0
  63. scitex/dev/plt/plot_stx_heatmap.py +28 -0
  64. scitex/dev/plt/plot_stx_image.py +28 -0
  65. scitex/dev/plt/plot_stx_imshow.py +28 -0
  66. scitex/dev/plt/plot_stx_joyplot.py +28 -0
  67. scitex/dev/plt/plot_stx_kde.py +28 -0
  68. scitex/dev/plt/plot_stx_line.py +28 -0
  69. scitex/dev/plt/plot_stx_mean_ci.py +28 -0
  70. scitex/dev/plt/plot_stx_mean_std.py +28 -0
  71. scitex/dev/plt/plot_stx_median_iqr.py +28 -0
  72. scitex/dev/plt/plot_stx_raster.py +28 -0
  73. scitex/dev/plt/plot_stx_rectangle.py +28 -0
  74. scitex/dev/plt/plot_stx_scatter.py +29 -0
  75. scitex/dev/plt/plot_stx_shaded_line.py +29 -0
  76. scitex/dev/plt/plot_stx_violin.py +28 -0
  77. scitex/dev/plt/plot_stx_violinplot.py +28 -0
  78. scitex/diagram/README.md +197 -0
  79. scitex/diagram/__init__.py +48 -0
  80. scitex/diagram/_compile.py +312 -0
  81. scitex/diagram/_diagram.py +355 -0
  82. scitex/diagram/_presets.py +173 -0
  83. scitex/diagram/_schema.py +182 -0
  84. scitex/diagram/_split.py +278 -0
  85. scitex/fig/__init__.py +352 -0
  86. scitex/{vis → fig}/backend/_parser.py +1 -1
  87. scitex/{vis → fig}/canvas.py +1 -1
  88. scitex/{vis → fig}/editor/__init__.py +5 -2
  89. scitex/{vis → fig}/editor/_dearpygui_editor.py +1 -1
  90. scitex/{vis → fig}/editor/_defaults.py +70 -5
  91. scitex/{vis → fig}/editor/_mpl_editor.py +1 -1
  92. scitex/{vis → fig}/editor/_qt_editor.py +182 -2
  93. scitex/{vis → fig}/editor/_tkinter_editor.py +1 -1
  94. scitex/fig/editor/edit/__init__.py +50 -0
  95. scitex/fig/editor/edit/backend_detector.py +109 -0
  96. scitex/fig/editor/edit/bundle_resolver.py +240 -0
  97. scitex/fig/editor/edit/editor_launcher.py +239 -0
  98. scitex/fig/editor/edit/manual_handler.py +53 -0
  99. scitex/fig/editor/edit/panel_loader.py +232 -0
  100. scitex/fig/editor/edit/path_resolver.py +67 -0
  101. scitex/fig/editor/flask_editor/_bbox.py +1299 -0
  102. scitex/fig/editor/flask_editor/_core.py +1429 -0
  103. scitex/{vis → fig}/editor/flask_editor/_plotter.py +38 -4
  104. scitex/fig/editor/flask_editor/_renderer.py +813 -0
  105. scitex/fig/editor/flask_editor/static/css/base/reset.css +41 -0
  106. scitex/fig/editor/flask_editor/static/css/base/typography.css +16 -0
  107. scitex/fig/editor/flask_editor/static/css/base/variables.css +85 -0
  108. scitex/fig/editor/flask_editor/static/css/components/buttons.css +217 -0
  109. scitex/fig/editor/flask_editor/static/css/components/context-menu.css +93 -0
  110. scitex/fig/editor/flask_editor/static/css/components/dropdown.css +57 -0
  111. scitex/fig/editor/flask_editor/static/css/components/forms.css +112 -0
  112. scitex/fig/editor/flask_editor/static/css/components/modal.css +59 -0
  113. scitex/fig/editor/flask_editor/static/css/components/sections.css +212 -0
  114. scitex/fig/editor/flask_editor/static/css/features/canvas.css +176 -0
  115. scitex/fig/editor/flask_editor/static/css/features/element-inspector.css +190 -0
  116. scitex/fig/editor/flask_editor/static/css/features/loading.css +59 -0
  117. scitex/fig/editor/flask_editor/static/css/features/overlay.css +45 -0
  118. scitex/fig/editor/flask_editor/static/css/features/panel-grid.css +95 -0
  119. scitex/fig/editor/flask_editor/static/css/features/selection.css +101 -0
  120. scitex/fig/editor/flask_editor/static/css/features/statistics.css +138 -0
  121. scitex/fig/editor/flask_editor/static/css/index.css +31 -0
  122. scitex/fig/editor/flask_editor/static/css/layout/container.css +7 -0
  123. scitex/fig/editor/flask_editor/static/css/layout/controls.css +56 -0
  124. scitex/fig/editor/flask_editor/static/css/layout/preview.css +78 -0
  125. scitex/fig/editor/flask_editor/static/js/alignment/axis.js +314 -0
  126. scitex/fig/editor/flask_editor/static/js/alignment/basic.js +107 -0
  127. scitex/fig/editor/flask_editor/static/js/alignment/distribute.js +54 -0
  128. scitex/fig/editor/flask_editor/static/js/canvas/canvas.js +172 -0
  129. scitex/fig/editor/flask_editor/static/js/canvas/dragging.js +258 -0
  130. scitex/fig/editor/flask_editor/static/js/canvas/resize.js +48 -0
  131. scitex/fig/editor/flask_editor/static/js/canvas/selection.js +71 -0
  132. scitex/fig/editor/flask_editor/static/js/core/api.js +288 -0
  133. scitex/fig/editor/flask_editor/static/js/core/state.js +143 -0
  134. scitex/fig/editor/flask_editor/static/js/core/utils.js +245 -0
  135. scitex/fig/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
  136. scitex/fig/editor/flask_editor/static/js/editor/bbox.js +339 -0
  137. scitex/fig/editor/flask_editor/static/js/editor/element-drag.js +286 -0
  138. scitex/fig/editor/flask_editor/static/js/editor/overlay.js +371 -0
  139. scitex/fig/editor/flask_editor/static/js/editor/preview.js +293 -0
  140. scitex/fig/editor/flask_editor/static/js/main.js +426 -0
  141. scitex/fig/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
  142. scitex/fig/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
  143. scitex/fig/editor/flask_editor/static/js/ui/controls.js +184 -0
  144. scitex/fig/editor/flask_editor/static/js/ui/download.js +57 -0
  145. scitex/fig/editor/flask_editor/static/js/ui/help.js +100 -0
  146. scitex/fig/editor/flask_editor/static/js/ui/theme.js +34 -0
  147. scitex/fig/editor/flask_editor/templates/__init__.py +123 -0
  148. scitex/fig/editor/flask_editor/templates/_html.py +852 -0
  149. scitex/fig/editor/flask_editor/templates/_scripts.py +4933 -0
  150. scitex/fig/editor/flask_editor/templates/_styles.py +1658 -0
  151. scitex/{vis → fig}/io/__init__.py +13 -1
  152. scitex/fig/io/_bundle.py +1058 -0
  153. scitex/{vis → fig}/io/_canvas.py +1 -1
  154. scitex/{vis → fig}/io/_data.py +1 -1
  155. scitex/{vis → fig}/io/_export.py +1 -1
  156. scitex/{vis → fig}/io/_load.py +1 -1
  157. scitex/{vis → fig}/io/_panel.py +1 -1
  158. scitex/{vis → fig}/io/_save.py +1 -1
  159. scitex/{vis → fig}/model/__init__.py +1 -1
  160. scitex/{vis → fig}/model/_annotations.py +1 -1
  161. scitex/{vis → fig}/model/_axes.py +1 -1
  162. scitex/{vis → fig}/model/_figure.py +1 -1
  163. scitex/{vis → fig}/model/_guides.py +1 -1
  164. scitex/{vis → fig}/model/_plot.py +1 -1
  165. scitex/{vis → fig}/model/_styles.py +1 -1
  166. scitex/{vis → fig}/utils/__init__.py +1 -1
  167. scitex/io/__init__.py +22 -26
  168. scitex/io/_bundle.py +493 -0
  169. scitex/io/_flush.py +5 -2
  170. scitex/io/_load.py +98 -0
  171. scitex/io/_load_modules/_H5Explorer.py +5 -2
  172. scitex/io/_load_modules/_canvas.py +2 -2
  173. scitex/io/_load_modules/_image.py +3 -4
  174. scitex/io/_load_modules/_txt.py +4 -2
  175. scitex/io/_metadata.py +34 -324
  176. scitex/io/_metadata_modules/__init__.py +46 -0
  177. scitex/io/_metadata_modules/_embed.py +70 -0
  178. scitex/io/_metadata_modules/_read.py +64 -0
  179. scitex/io/_metadata_modules/_utils.py +79 -0
  180. scitex/io/_metadata_modules/embed_metadata_jpeg.py +74 -0
  181. scitex/io/_metadata_modules/embed_metadata_pdf.py +53 -0
  182. scitex/io/_metadata_modules/embed_metadata_png.py +26 -0
  183. scitex/io/_metadata_modules/embed_metadata_svg.py +62 -0
  184. scitex/io/_metadata_modules/read_metadata_jpeg.py +57 -0
  185. scitex/io/_metadata_modules/read_metadata_pdf.py +51 -0
  186. scitex/io/_metadata_modules/read_metadata_png.py +39 -0
  187. scitex/io/_metadata_modules/read_metadata_svg.py +44 -0
  188. scitex/io/_qr_utils.py +5 -3
  189. scitex/io/_save.py +548 -30
  190. scitex/io/_save_modules/_canvas.py +3 -3
  191. scitex/io/_save_modules/_image.py +5 -9
  192. scitex/io/_save_modules/_tex.py +7 -4
  193. scitex/io/_zip_bundle.py +439 -0
  194. scitex/io/utils/h5_to_zarr.py +11 -9
  195. scitex/msword/__init__.py +255 -0
  196. scitex/msword/profiles.py +357 -0
  197. scitex/msword/reader.py +753 -0
  198. scitex/msword/utils.py +289 -0
  199. scitex/msword/writer.py +362 -0
  200. scitex/plt/__init__.py +5 -2
  201. scitex/plt/_subplots/_AxesWrapper.py +6 -6
  202. scitex/plt/_subplots/_AxisWrapper.py +15 -9
  203. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +36 -0
  204. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +264 -0
  205. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +213 -0
  206. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +128 -0
  207. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +59 -0
  208. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +34 -0
  209. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +593 -0
  210. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +654 -0
  211. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +527 -0
  212. scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +321 -0
  213. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +33 -0
  214. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +152 -0
  215. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +600 -0
  216. scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +79 -5
  217. scitex/plt/_subplots/_FigWrapper.py +6 -6
  218. scitex/plt/_subplots/_SubplotsWrapper.py +28 -18
  219. scitex/plt/_subplots/_export_as_csv.py +35 -5
  220. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +8 -0
  221. scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +10 -21
  222. scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +18 -7
  223. scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +28 -12
  224. scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +10 -4
  225. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +13 -1
  226. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +12 -2
  227. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +10 -3
  228. scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +10 -4
  229. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +18 -3
  230. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +44 -36
  231. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +14 -2
  232. scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +11 -5
  233. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +84 -0
  234. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +85 -0
  235. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_conf_mat.py +14 -3
  236. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_contour.py +54 -0
  237. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +14 -2
  238. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +120 -0
  239. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +16 -6
  240. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +29 -19
  241. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_imshow.py +63 -0
  242. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +22 -5
  243. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +18 -14
  244. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +18 -14
  245. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +18 -14
  246. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +10 -2
  247. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +51 -0
  248. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter_hist.py +18 -9
  249. scitex/plt/ax/_plot/_stx_ecdf.py +4 -2
  250. scitex/plt/gallery/_generate.py +421 -14
  251. scitex/plt/io/__init__.py +53 -0
  252. scitex/plt/io/_bundle.py +490 -0
  253. scitex/plt/io/_layered_bundle.py +1343 -0
  254. scitex/plt/styles/SCITEX_STYLE.yaml +26 -0
  255. scitex/plt/styles/__init__.py +14 -0
  256. scitex/plt/styles/presets.py +78 -0
  257. scitex/plt/utils/__init__.py +13 -1
  258. scitex/plt/utils/_collect_figure_metadata.py +10 -14
  259. scitex/plt/utils/_configure_mpl.py +6 -18
  260. scitex/plt/utils/_crop.py +32 -14
  261. scitex/plt/utils/_csv_column_naming.py +54 -0
  262. scitex/plt/utils/_figure_mm.py +116 -1
  263. scitex/plt/utils/_hitmap.py +1643 -0
  264. scitex/plt/utils/metadata/__init__.py +25 -0
  265. scitex/plt/utils/metadata/_core.py +9 -10
  266. scitex/plt/utils/metadata/_dimensions.py +6 -3
  267. scitex/plt/utils/metadata/_editable_export.py +405 -0
  268. scitex/plt/utils/metadata/_geometry_extraction.py +570 -0
  269. scitex/schema/__init__.py +109 -16
  270. scitex/schema/_canvas.py +1 -1
  271. scitex/schema/_plot.py +1015 -0
  272. scitex/schema/_stats.py +2 -2
  273. scitex/stats/__init__.py +117 -0
  274. scitex/stats/io/__init__.py +29 -0
  275. scitex/stats/io/_bundle.py +156 -0
  276. scitex/tex/__init__.py +4 -0
  277. scitex/tex/_export.py +890 -0
  278. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/METADATA +11 -1
  279. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/RECORD +294 -170
  280. scitex/io/memo.md +0 -2827
  281. scitex/plt/REQUESTS.md +0 -191
  282. scitex/plt/_subplots/TODO.md +0 -53
  283. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin.py +0 -559
  284. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +0 -1609
  285. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +0 -447
  286. scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_between.json +0 -110
  287. scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_betweenx.json +0 -88
  288. scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fill_between.json +0 -103
  289. scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fillv.json +0 -106
  290. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/bar.json +0 -92
  291. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/barh.json +0 -92
  292. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/boxplot.json +0 -92
  293. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_bar.json +0 -84
  294. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_barh.json +0 -84
  295. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_box.json +0 -83
  296. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_boxplot.json +0 -93
  297. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violin.json +0 -91
  298. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violinplot.json +0 -91
  299. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/violinplot.json +0 -91
  300. scitex/plt/templates/research-master/scitex/vis/gallery/contour/contour.json +0 -97
  301. scitex/plt/templates/research-master/scitex/vis/gallery/contour/contourf.json +0 -98
  302. scitex/plt/templates/research-master/scitex/vis/gallery/contour/stx_contour.json +0 -84
  303. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist.json +0 -101
  304. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist2d.json +0 -96
  305. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_ecdf.json +0 -95
  306. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_joyplot.json +0 -95
  307. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_kde.json +0 -93
  308. scitex/plt/templates/research-master/scitex/vis/gallery/grid/imshow.json +0 -95
  309. scitex/plt/templates/research-master/scitex/vis/gallery/grid/matshow.json +0 -95
  310. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_conf_mat.json +0 -83
  311. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_heatmap.json +0 -92
  312. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_image.json +0 -121
  313. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_imshow.json +0 -84
  314. scitex/plt/templates/research-master/scitex/vis/gallery/line/plot.json +0 -110
  315. scitex/plt/templates/research-master/scitex/vis/gallery/line/step.json +0 -92
  316. scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_line.json +0 -95
  317. scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_shaded_line.json +0 -96
  318. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/hexbin.json +0 -95
  319. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/scatter.json +0 -95
  320. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stem.json +0 -92
  321. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stx_scatter.json +0 -84
  322. scitex/plt/templates/research-master/scitex/vis/gallery/special/pie.json +0 -94
  323. scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_raster.json +0 -109
  324. scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_rectangle.json +0 -108
  325. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/errorbar.json +0 -93
  326. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_errorbar.json +0 -84
  327. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_ci.json +0 -96
  328. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_std.json +0 -96
  329. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_median_iqr.json +0 -96
  330. scitex/plt/templates/research-master/scitex/vis/gallery/vector/quiver.json +0 -99
  331. scitex/plt/templates/research-master/scitex/vis/gallery/vector/streamplot.json +0 -100
  332. scitex/vis/__init__.py +0 -177
  333. scitex/vis/editor/_edit.py +0 -390
  334. scitex/vis/editor/flask_editor/_bbox.py +0 -529
  335. scitex/vis/editor/flask_editor/_core.py +0 -168
  336. scitex/vis/editor/flask_editor/_renderer.py +0 -393
  337. scitex/vis/editor/flask_editor/templates/__init__.py +0 -33
  338. scitex/vis/editor/flask_editor/templates/_html.py +0 -513
  339. scitex/vis/editor/flask_editor/templates/_scripts.py +0 -1261
  340. scitex/vis/editor/flask_editor/templates/_styles.py +0 -739
  341. /scitex/{vis → fig}/README.md +0 -0
  342. /scitex/{vis → fig}/backend/__init__.py +0 -0
  343. /scitex/{vis → fig}/backend/_export.py +0 -0
  344. /scitex/{vis → fig}/backend/_render.py +0 -0
  345. /scitex/{vis → fig}/docs/CANVAS_ARCHITECTURE.md +0 -0
  346. /scitex/{vis → fig}/editor/_flask_editor.py +0 -0
  347. /scitex/{vis → fig}/editor/flask_editor/__init__.py +0 -0
  348. /scitex/{vis → fig}/editor/flask_editor/_utils.py +0 -0
  349. /scitex/{vis → fig}/io/_directory.py +0 -0
  350. /scitex/{vis → fig}/model/_plot_types.py +0 -0
  351. /scitex/{vis → fig}/utils/_defaults.py +0 -0
  352. /scitex/{vis → fig}/utils/_validate.py +0 -0
  353. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/WHEEL +0 -0
  354. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/entry_points.txt +0 -0
  355. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/licenses/LICENSE +0 -0
scitex/schema/_plot.py ADDED
@@ -0,0 +1,1015 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # Timestamp: "2025-12-13 (ywatanabe)"
4
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/schema/_plot.py
5
+
6
+ """
7
+ Plot Schema - Canonical source of truth for plot specifications.
8
+
9
+ This module defines the layered schema architecture for plots:
10
+
11
+ 1. PlotSpec (spec.json) - Semantic definition: WHAT to plot
12
+ - Traces with type and column mappings
13
+ - Axes configuration (labels, limits)
14
+ - Data source reference
15
+
16
+ 2. PlotStyle (style.json) - Appearance: HOW it looks
17
+ - Theme and colors
18
+ - Line widths, marker sizes
19
+ - Font settings
20
+
21
+ 3. PlotGeometry (cache/geometry_px.json) - Rendered positions: WHERE (cache)
22
+ - Pixel coordinates
23
+ - Path data for hit testing
24
+ - Bounding boxes in px
25
+
26
+ 4. RenderManifest (cache/render_manifest.json) - Render metadata
27
+ - DPI, figure size
28
+ - Source hash for cache invalidation
29
+
30
+ Design Principles:
31
+ - Canonical data uses ratio (0-1) for axes bbox, mm for panel size
32
+ - px data is ALWAYS derived/cached, never source of truth
33
+ - Traces are semantic (boxplot, heatmap) not decomposed (line segments)
34
+ """
35
+
36
+ from dataclasses import dataclass, field, asdict
37
+ from typing import Dict, Any, List, Optional, Literal, Union
38
+ from datetime import datetime
39
+ import json
40
+
41
+
42
+ # Schema versions
43
+ PLOT_SPEC_VERSION = "1.0.0"
44
+ PLOT_STYLE_VERSION = "1.0.0"
45
+ PLOT_GEOMETRY_VERSION = "1.0.0"
46
+
47
+ # DPI fallback for legacy data without explicit DPI
48
+ # Note: For dynamic DPI resolution, use scitex.plt.styles.get_default_dpi()
49
+ # This constant is only used as a fallback when parsing data without DPI info
50
+ DPI_FALLBACK = 300
51
+
52
+
53
+ # =============================================================================
54
+ # Type Aliases
55
+ # =============================================================================
56
+
57
+ TraceType = Literal[
58
+ # Line-based
59
+ "line", "step", "stem",
60
+ # Scatter-based
61
+ "scatter", "hexbin",
62
+ # Distribution
63
+ "histogram", "kde", "ecdf", "boxplot", "violinplot", "joyplot",
64
+ # Categorical
65
+ "bar", "barh",
66
+ # 2D/Grid
67
+ "heatmap", "imshow", "contour", "contourf", "pcolormesh",
68
+ # Statistical
69
+ "errorbar", "fill_between", "mean_std", "mean_ci", "median_iqr",
70
+ # Vector
71
+ "quiver", "streamplot",
72
+ # Special
73
+ "pie", "raster", "rectangle",
74
+ # Generic fallback
75
+ "unknown",
76
+ ]
77
+
78
+ CoordinateSpace = Literal["panel", "figure", "data"]
79
+
80
+
81
+ # =============================================================================
82
+ # Bounding Box Specs
83
+ # =============================================================================
84
+
85
+
86
+ @dataclass
87
+ class BboxRatio:
88
+ """
89
+ Bounding box in normalized coordinates (0-1).
90
+
91
+ This is the CANONICAL representation for axes position within a panel.
92
+ """
93
+ x0: float
94
+ y0: float
95
+ width: float
96
+ height: float
97
+ space: CoordinateSpace = "panel"
98
+
99
+ @property
100
+ def x1(self) -> float:
101
+ return self.x0 + self.width
102
+
103
+ @property
104
+ def y1(self) -> float:
105
+ return self.y0 + self.height
106
+
107
+ def to_dict(self) -> Dict[str, Any]:
108
+ return {
109
+ "x0": self.x0,
110
+ "y0": self.y0,
111
+ "width": self.width,
112
+ "height": self.height,
113
+ "space": self.space,
114
+ }
115
+
116
+ @classmethod
117
+ def from_dict(cls, data: Dict[str, Any]) -> "BboxRatio":
118
+ # Handle both width/height and x1/y1 formats
119
+ if "width" not in data and "x1" in data:
120
+ data = data.copy()
121
+ data["width"] = data["x1"] - data["x0"]
122
+ data["height"] = data["y1"] - data["y0"]
123
+ data.pop("x1", None)
124
+ data.pop("y1", None)
125
+ return cls(**{k: v for k, v in data.items() if k in ["x0", "y0", "width", "height", "space"]})
126
+
127
+
128
+ @dataclass
129
+ class BboxPx:
130
+ """
131
+ Bounding box in pixel coordinates.
132
+
133
+ This is DERIVED/CACHED, not canonical.
134
+ """
135
+ x0: float
136
+ y0: float
137
+ width: float
138
+ height: float
139
+
140
+ @property
141
+ def x1(self) -> float:
142
+ return self.x0 + self.width
143
+
144
+ @property
145
+ def y1(self) -> float:
146
+ return self.y0 + self.height
147
+
148
+ def to_dict(self) -> Dict[str, Any]:
149
+ return {
150
+ "x0": self.x0,
151
+ "y0": self.y0,
152
+ "x1": self.x1,
153
+ "y1": self.y1,
154
+ "width": self.width,
155
+ "height": self.height,
156
+ }
157
+
158
+ @classmethod
159
+ def from_dict(cls, data: Dict[str, Any]) -> "BboxPx":
160
+ if "width" not in data and "x1" in data:
161
+ data = data.copy()
162
+ data["width"] = data["x1"] - data["x0"]
163
+ data["height"] = data["y1"] - data["y0"]
164
+ return cls(
165
+ x0=data["x0"],
166
+ y0=data["y0"],
167
+ width=data.get("width", 0),
168
+ height=data.get("height", 0),
169
+ )
170
+
171
+
172
+ # =============================================================================
173
+ # Trace Specification (Semantic)
174
+ # =============================================================================
175
+
176
+
177
+ @dataclass
178
+ class TraceSpec:
179
+ """
180
+ Semantic specification for a single trace/artist.
181
+
182
+ This captures WHAT the user intended to plot, not how it was rendered.
183
+
184
+ Parameters
185
+ ----------
186
+ id : str
187
+ Unique identifier for this trace
188
+ type : TraceType
189
+ Semantic type (boxplot, heatmap, line, etc.)
190
+ x_col : str, optional
191
+ Column name for x data (line, scatter, bar, etc.)
192
+ y_col : str, optional
193
+ Column name for y data (line, scatter)
194
+ data_cols : list, optional
195
+ Column names for multi-column data (boxplot, violinplot)
196
+ value_col : str, optional
197
+ Column name for values (heatmap, contour)
198
+ u_col, v_col : str, optional
199
+ Column names for vector components (quiver, streamplot)
200
+ label : str, optional
201
+ Legend label
202
+ group : str, optional
203
+ Grouping identifier for related traces
204
+
205
+ Examples
206
+ --------
207
+ >>> # Line plot
208
+ >>> TraceSpec(id="line-0", type="line", x_col="time", y_col="signal", label="EEG")
209
+
210
+ >>> # Boxplot with 4 groups
211
+ >>> TraceSpec(id="box-0", type="boxplot", data_cols=["A", "B", "C", "D"])
212
+
213
+ >>> # Heatmap
214
+ >>> TraceSpec(id="hmap-0", type="heatmap", x_col="x", y_col="y", value_col="z")
215
+
216
+ >>> # Quiver (vector field)
217
+ >>> TraceSpec(id="quiv-0", type="quiver", x_col="x", y_col="y", u_col="u", v_col="v")
218
+ """
219
+ id: str
220
+ type: TraceType
221
+
222
+ # Column mappings (usage depends on trace type)
223
+ x_col: Optional[str] = None
224
+ y_col: Optional[str] = None
225
+ data_cols: Optional[List[str]] = None # For boxplot, violin, etc.
226
+ value_col: Optional[str] = None # For heatmap, contour
227
+ u_col: Optional[str] = None # For quiver
228
+ v_col: Optional[str] = None # For quiver
229
+
230
+ # Metadata
231
+ label: Optional[str] = None
232
+ group: Optional[str] = None
233
+ axes_index: int = 0 # Which axes this trace belongs to
234
+
235
+ # Additional type-specific parameters
236
+ extra: Dict[str, Any] = field(default_factory=dict)
237
+
238
+ def to_dict(self) -> Dict[str, Any]:
239
+ result = {
240
+ "id": self.id,
241
+ "type": self.type,
242
+ "axes_index": self.axes_index,
243
+ }
244
+ # Only include non-None fields
245
+ if self.x_col:
246
+ result["x_col"] = self.x_col
247
+ if self.y_col:
248
+ result["y_col"] = self.y_col
249
+ if self.data_cols:
250
+ result["data_cols"] = self.data_cols
251
+ if self.value_col:
252
+ result["value_col"] = self.value_col
253
+ if self.u_col:
254
+ result["u_col"] = self.u_col
255
+ if self.v_col:
256
+ result["v_col"] = self.v_col
257
+ if self.label:
258
+ result["label"] = self.label
259
+ if self.group:
260
+ result["group"] = self.group
261
+ if self.extra:
262
+ result["extra"] = self.extra
263
+ return result
264
+
265
+ @classmethod
266
+ def from_dict(cls, data: Dict[str, Any]) -> "TraceSpec":
267
+ return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
268
+
269
+
270
+ # =============================================================================
271
+ # Axes Specification (Semantic)
272
+ # =============================================================================
273
+
274
+
275
+ @dataclass
276
+ class AxesLimits:
277
+ """Axis limits specification."""
278
+ x: Optional[List[float]] = None # [xmin, xmax]
279
+ y: Optional[List[float]] = None # [ymin, ymax]
280
+
281
+ def to_dict(self) -> Dict[str, Any]:
282
+ result = {}
283
+ if self.x:
284
+ result["x"] = self.x
285
+ if self.y:
286
+ result["y"] = self.y
287
+ return result
288
+
289
+ @classmethod
290
+ def from_dict(cls, data: Dict[str, Any]) -> "AxesLimits":
291
+ return cls(x=data.get("x"), y=data.get("y"))
292
+
293
+
294
+ @dataclass
295
+ class AxesLabels:
296
+ """Axes labels specification."""
297
+ xlabel: Optional[str] = None
298
+ ylabel: Optional[str] = None
299
+ title: Optional[str] = None
300
+
301
+ def to_dict(self) -> Dict[str, Any]:
302
+ result = {}
303
+ if self.xlabel:
304
+ result["xlabel"] = self.xlabel
305
+ if self.ylabel:
306
+ result["ylabel"] = self.ylabel
307
+ if self.title:
308
+ result["title"] = self.title
309
+ return result
310
+
311
+ @classmethod
312
+ def from_dict(cls, data: Dict[str, Any]) -> "AxesLabels":
313
+ return cls(**{k: v for k, v in data.items() if k in ["xlabel", "ylabel", "title"]})
314
+
315
+
316
+ @dataclass
317
+ class AxesSpecItem:
318
+ """
319
+ Specification for a single axes within a plot.
320
+
321
+ Parameters
322
+ ----------
323
+ id : str
324
+ Unique identifier (e.g., "ax0", "colorbar")
325
+ bbox : BboxRatio
326
+ Position in normalized coordinates (0-1) within the panel
327
+ labels : AxesLabels
328
+ Axis labels and title
329
+ limits : AxesLimits, optional
330
+ Axis limits (auto if not specified)
331
+ role : str
332
+ Role of this axes ("main", "colorbar", "inset", etc.)
333
+ linked_to : str, optional
334
+ ID of axes this is linked to (e.g., colorbar linked to heatmap axes)
335
+ """
336
+ id: str
337
+ bbox: BboxRatio
338
+ labels: AxesLabels = field(default_factory=AxesLabels)
339
+ limits: Optional[AxesLimits] = None
340
+ role: str = "main" # "main", "colorbar", "inset", "twinx", "twiny"
341
+ linked_to: Optional[str] = None
342
+
343
+ def to_dict(self) -> Dict[str, Any]:
344
+ result = {
345
+ "id": self.id,
346
+ "bbox": self.bbox.to_dict(),
347
+ "labels": self.labels.to_dict(),
348
+ "role": self.role,
349
+ }
350
+ if self.limits:
351
+ result["limits"] = self.limits.to_dict()
352
+ if self.linked_to:
353
+ result["linked_to"] = self.linked_to
354
+ return result
355
+
356
+ @classmethod
357
+ def from_dict(cls, data: Dict[str, Any]) -> "AxesSpecItem":
358
+ data_copy = data.copy()
359
+ if "bbox" in data_copy:
360
+ data_copy["bbox"] = BboxRatio.from_dict(data_copy["bbox"])
361
+ else:
362
+ # Default bbox
363
+ data_copy["bbox"] = BboxRatio(x0=0.15, y0=0.15, width=0.7, height=0.7)
364
+ if "labels" in data_copy:
365
+ data_copy["labels"] = AxesLabels.from_dict(data_copy["labels"])
366
+ if "limits" in data_copy and data_copy["limits"]:
367
+ data_copy["limits"] = AxesLimits.from_dict(data_copy["limits"])
368
+ return cls(**{k: v for k, v in data_copy.items() if k in cls.__dataclass_fields__})
369
+
370
+
371
+ # =============================================================================
372
+ # Data Source Specification
373
+ # =============================================================================
374
+
375
+
376
+ @dataclass
377
+ class DataSourceSpec:
378
+ """
379
+ Specification for the data source.
380
+
381
+ Parameters
382
+ ----------
383
+ csv : str
384
+ Relative path to CSV file
385
+ format : str
386
+ Data format ("wide" or "long")
387
+ hash : str, optional
388
+ Content hash for integrity verification
389
+ """
390
+ csv: str
391
+ format: str = "wide"
392
+ hash: Optional[str] = None
393
+
394
+ def to_dict(self) -> Dict[str, Any]:
395
+ result = {"csv": self.csv, "format": self.format}
396
+ if self.hash:
397
+ result["hash"] = self.hash
398
+ return result
399
+
400
+ @classmethod
401
+ def from_dict(cls, data: Dict[str, Any]) -> "DataSourceSpec":
402
+ # Handle legacy "source" or "path" keys
403
+ csv = data.get("csv") or data.get("source") or data.get("path", "")
404
+ return cls(
405
+ csv=csv,
406
+ format=data.get("format", "wide"),
407
+ hash=data.get("hash"),
408
+ )
409
+
410
+
411
+ # =============================================================================
412
+ # PlotSpec - Main Semantic Specification
413
+ # =============================================================================
414
+
415
+
416
+ @dataclass
417
+ class PlotSpec:
418
+ """
419
+ Complete semantic specification for a plot.
420
+
421
+ This is the SOURCE OF TRUTH stored in spec.json.
422
+ Contains only semantic information about WHAT to plot.
423
+
424
+ Parameters
425
+ ----------
426
+ plot_id : str
427
+ Unique identifier for this plot
428
+ data : DataSourceSpec
429
+ Data source specification
430
+ axes : list of AxesSpecItem
431
+ Axes configurations
432
+ traces : list of TraceSpec
433
+ Trace/artist specifications
434
+
435
+ Examples
436
+ --------
437
+ >>> spec = PlotSpec(
438
+ ... plot_id="panel_A",
439
+ ... data=DataSourceSpec(csv="data.csv"),
440
+ ... axes=[AxesSpecItem(id="ax0", bbox=BboxRatio(0.15, 0.15, 0.7, 0.7))],
441
+ ... traces=[TraceSpec(id="line-0", type="line", x_col="x", y_col="y")]
442
+ ... )
443
+ >>> spec.to_json()
444
+ """
445
+ plot_id: str
446
+ data: DataSourceSpec
447
+ axes: List[AxesSpecItem] = field(default_factory=list)
448
+ traces: List[TraceSpec] = field(default_factory=list)
449
+
450
+ # Schema metadata
451
+ scitex_schema: str = "scitex.plt.spec"
452
+ scitex_schema_version: str = PLOT_SPEC_VERSION
453
+
454
+ def to_dict(self) -> Dict[str, Any]:
455
+ return {
456
+ "schema": {
457
+ "name": self.scitex_schema,
458
+ "version": self.scitex_schema_version,
459
+ },
460
+ "plot_id": self.plot_id,
461
+ "data": self.data.to_dict(),
462
+ "axes": [ax.to_dict() for ax in self.axes],
463
+ "traces": [tr.to_dict() for tr in self.traces],
464
+ }
465
+
466
+ def to_json(self, indent: int = 2) -> str:
467
+ return json.dumps(self.to_dict(), indent=indent)
468
+
469
+ @classmethod
470
+ def from_dict(cls, data: Dict[str, Any]) -> "PlotSpec":
471
+ schema_info = data.get("schema", {})
472
+ return cls(
473
+ plot_id=data.get("plot_id", ""),
474
+ data=DataSourceSpec.from_dict(data.get("data", {})),
475
+ axes=[AxesSpecItem.from_dict(ax) for ax in data.get("axes", [])],
476
+ traces=[TraceSpec.from_dict(tr) for tr in data.get("traces", [])],
477
+ scitex_schema=schema_info.get("name", "scitex.plt.spec"),
478
+ scitex_schema_version=schema_info.get("version", PLOT_SPEC_VERSION),
479
+ )
480
+
481
+ @classmethod
482
+ def from_json(cls, json_str: str) -> "PlotSpec":
483
+ return cls.from_dict(json.loads(json_str))
484
+
485
+
486
+ # =============================================================================
487
+ # PlotStyle - Appearance Specification
488
+ # =============================================================================
489
+
490
+
491
+ @dataclass
492
+ class TraceStyleSpec:
493
+ """Style overrides for a specific trace."""
494
+ trace_id: str
495
+ color: Optional[str] = None
496
+ linewidth: Optional[float] = None
497
+ linestyle: Optional[str] = None
498
+ marker: Optional[str] = None
499
+ markersize: Optional[float] = None
500
+ alpha: Optional[float] = None
501
+ extra: Dict[str, Any] = field(default_factory=dict)
502
+
503
+ def to_dict(self) -> Dict[str, Any]:
504
+ result = {"trace_id": self.trace_id}
505
+ for field_name in ["color", "linewidth", "linestyle", "marker", "markersize", "alpha"]:
506
+ val = getattr(self, field_name)
507
+ if val is not None:
508
+ result[field_name] = val
509
+ if self.extra:
510
+ result["extra"] = self.extra
511
+ return result
512
+
513
+ @classmethod
514
+ def from_dict(cls, data: Dict[str, Any]) -> "TraceStyleSpec":
515
+ return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
516
+
517
+
518
+ @dataclass
519
+ class ThemeSpec:
520
+ """Theme specification."""
521
+ mode: str = "light" # "light", "dark", "auto"
522
+ colors: Dict[str, str] = field(default_factory=lambda: {
523
+ "background": "transparent",
524
+ "axes_bg": "white",
525
+ "text": "black",
526
+ "spine": "black",
527
+ "tick": "black",
528
+ })
529
+ palette: Optional[str] = None # Color palette name
530
+
531
+ def to_dict(self) -> Dict[str, Any]:
532
+ result = {"mode": self.mode, "colors": self.colors}
533
+ if self.palette:
534
+ result["palette"] = self.palette
535
+ return result
536
+
537
+ @classmethod
538
+ def from_dict(cls, data: Dict[str, Any]) -> "ThemeSpec":
539
+ return cls(
540
+ mode=data.get("mode", "light"),
541
+ colors=data.get("colors", {}),
542
+ palette=data.get("palette"),
543
+ )
544
+
545
+
546
+ @dataclass
547
+ class FontSpec:
548
+ """Font specification."""
549
+ family: str = "sans-serif"
550
+ size_pt: float = 7.0
551
+ title_size_pt: float = 8.0
552
+ label_size_pt: float = 7.0
553
+ tick_size_pt: float = 6.0
554
+
555
+ def to_dict(self) -> Dict[str, Any]:
556
+ return asdict(self)
557
+
558
+ @classmethod
559
+ def from_dict(cls, data: Dict[str, Any]) -> "FontSpec":
560
+ return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
561
+
562
+
563
+ @dataclass
564
+ class SizeSpec:
565
+ """Panel size specification (canonical in mm)."""
566
+ width_mm: float = 80.0
567
+ height_mm: float = 68.0
568
+
569
+ def to_dict(self) -> Dict[str, Any]:
570
+ return asdict(self)
571
+
572
+ @classmethod
573
+ def from_dict(cls, data: Dict[str, Any]) -> "SizeSpec":
574
+ return cls(
575
+ width_mm=data.get("width_mm", 80.0),
576
+ height_mm=data.get("height_mm", 68.0),
577
+ )
578
+
579
+
580
+ # Valid matplotlib legend location strings
581
+ LegendLocation = Literal[
582
+ "best", "upper right", "upper left", "lower left", "lower right",
583
+ "right", "center left", "center right", "lower center", "upper center",
584
+ "center",
585
+ ]
586
+
587
+
588
+ @dataclass
589
+ class LegendSpec:
590
+ """
591
+ Legend configuration specification.
592
+
593
+ Parameters
594
+ ----------
595
+ visible : bool
596
+ Whether to show the legend (default True)
597
+ location : str
598
+ Legend location. Valid values:
599
+ - "best" (auto-placement)
600
+ - "upper right", "upper left", "lower right", "lower left"
601
+ - "right", "center left", "center right"
602
+ - "upper center", "lower center", "center"
603
+ frameon : bool
604
+ Whether to draw a frame around the legend
605
+ fontsize : float, optional
606
+ Font size for legend text (in points)
607
+ ncols : int
608
+ Number of columns in the legend
609
+ title : str, optional
610
+ Legend title
611
+ """
612
+ visible: bool = True
613
+ location: str = "best"
614
+ frameon: bool = True
615
+ fontsize: Optional[float] = None
616
+ ncols: int = 1
617
+ title: Optional[str] = None
618
+
619
+ def to_dict(self) -> Dict[str, Any]:
620
+ result = {
621
+ "visible": self.visible,
622
+ "location": self.location,
623
+ "frameon": self.frameon,
624
+ "ncols": self.ncols,
625
+ }
626
+ if self.fontsize is not None:
627
+ result["fontsize"] = self.fontsize
628
+ if self.title is not None:
629
+ result["title"] = self.title
630
+ return result
631
+
632
+ @classmethod
633
+ def from_dict(cls, data: Dict[str, Any]) -> "LegendSpec":
634
+ # Handle backward compatibility: boolean legend value
635
+ if isinstance(data, bool):
636
+ return cls(visible=data)
637
+ return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
638
+
639
+
640
+ @dataclass
641
+ class PlotStyle:
642
+ """
643
+ Appearance specification for a plot.
644
+
645
+ Stored in style.json. Contains HOW the plot looks.
646
+ Only stores overrides from defaults.
647
+
648
+ Parameters
649
+ ----------
650
+ theme : ThemeSpec
651
+ Theme configuration
652
+ size : SizeSpec
653
+ Panel size in mm (canonical unit)
654
+ font : FontSpec
655
+ Font settings
656
+ traces : list of TraceStyleSpec
657
+ Per-trace style overrides
658
+ legend : LegendSpec
659
+ Legend configuration (visibility, location, styling)
660
+ grid : bool
661
+ Whether to show grid lines
662
+ """
663
+ theme: ThemeSpec = field(default_factory=ThemeSpec)
664
+ size: SizeSpec = field(default_factory=SizeSpec)
665
+ font: FontSpec = field(default_factory=FontSpec)
666
+ traces: List[TraceStyleSpec] = field(default_factory=list)
667
+ legend: LegendSpec = field(default_factory=LegendSpec)
668
+
669
+ # Axes-level overrides
670
+ grid: bool = False
671
+
672
+ # Schema metadata
673
+ scitex_schema: str = "scitex.plt.style"
674
+ scitex_schema_version: str = PLOT_STYLE_VERSION
675
+
676
+ def to_dict(self) -> Dict[str, Any]:
677
+ return {
678
+ "schema": {
679
+ "name": self.scitex_schema,
680
+ "version": self.scitex_schema_version,
681
+ },
682
+ "theme": self.theme.to_dict(),
683
+ "size": self.size.to_dict(),
684
+ "font": self.font.to_dict(),
685
+ "traces": [tr.to_dict() for tr in self.traces],
686
+ "legend": self.legend.to_dict(),
687
+ "grid": self.grid,
688
+ }
689
+
690
+ def to_json(self, indent: int = 2) -> str:
691
+ return json.dumps(self.to_dict(), indent=indent)
692
+
693
+ @classmethod
694
+ def from_dict(cls, data: Dict[str, Any]) -> "PlotStyle":
695
+ # Handle backward compatibility: legend can be bool or dict
696
+ legend_data = data.get("legend", True)
697
+ if isinstance(legend_data, bool):
698
+ legend = LegendSpec(visible=legend_data)
699
+ elif isinstance(legend_data, dict):
700
+ legend = LegendSpec.from_dict(legend_data)
701
+ else:
702
+ legend = LegendSpec()
703
+
704
+ return cls(
705
+ theme=ThemeSpec.from_dict(data.get("theme", {})),
706
+ size=SizeSpec.from_dict(data.get("size", {})),
707
+ font=FontSpec.from_dict(data.get("font", {})),
708
+ traces=[TraceStyleSpec.from_dict(tr) for tr in data.get("traces", [])],
709
+ legend=legend,
710
+ grid=data.get("grid", False),
711
+ )
712
+
713
+ @classmethod
714
+ def from_json(cls, json_str: str) -> "PlotStyle":
715
+ return cls.from_dict(json.loads(json_str))
716
+
717
+
718
+ # =============================================================================
719
+ # PlotGeometry - Cached Render Output
720
+ # =============================================================================
721
+
722
+
723
+ @dataclass
724
+ class RenderedArtist:
725
+ """
726
+ Cached pixel-level data for a rendered artist.
727
+
728
+ This is DERIVED from PlotSpec + PlotStyle, not source of truth.
729
+ """
730
+ id: str
731
+ type: str
732
+ axes_index: int
733
+ label: Optional[str] = None
734
+ bbox_px: Optional[BboxPx] = None
735
+ path_px: Optional[List[List[float]]] = None # [[x, y], [x, y], ...]
736
+ extra: Dict[str, Any] = field(default_factory=dict)
737
+
738
+ def to_dict(self) -> Dict[str, Any]:
739
+ result = {
740
+ "id": self.id,
741
+ "type": self.type,
742
+ "axes_index": self.axes_index,
743
+ }
744
+ if self.label:
745
+ result["label"] = self.label
746
+ if self.bbox_px:
747
+ result["bbox_px"] = self.bbox_px.to_dict()
748
+ if self.path_px:
749
+ result["path_px"] = self.path_px
750
+ if self.extra:
751
+ result.update(self.extra)
752
+ return result
753
+
754
+ @classmethod
755
+ def from_dict(cls, data: Dict[str, Any]) -> "RenderedArtist":
756
+ data_copy = data.copy()
757
+ if "bbox_px" in data_copy and data_copy["bbox_px"]:
758
+ data_copy["bbox_px"] = BboxPx.from_dict(data_copy["bbox_px"])
759
+ return cls(**{k: v for k, v in data_copy.items() if k in cls.__dataclass_fields__})
760
+
761
+
762
+ @dataclass
763
+ class RenderedAxes:
764
+ """Cached pixel-level data for rendered axes."""
765
+ id: str
766
+ xlim: List[float]
767
+ ylim: List[float]
768
+ bbox_px: BboxPx
769
+
770
+ def to_dict(self) -> Dict[str, Any]:
771
+ return {
772
+ "id": self.id,
773
+ "xlim": self.xlim,
774
+ "ylim": self.ylim,
775
+ "bbox_px": self.bbox_px.to_dict(),
776
+ }
777
+
778
+ @classmethod
779
+ def from_dict(cls, data: Dict[str, Any]) -> "RenderedAxes":
780
+ return cls(
781
+ id=data.get("id", "ax0"),
782
+ xlim=data.get("xlim", [0, 1]),
783
+ ylim=data.get("ylim", [0, 1]),
784
+ bbox_px=BboxPx.from_dict(data.get("bbox_px", {"x0": 0, "y0": 0, "width": 100, "height": 100})),
785
+ )
786
+
787
+
788
+ @dataclass
789
+ class HitRegionEntry:
790
+ """Entry in the hit region color map."""
791
+ id: int
792
+ type: str
793
+ label: str
794
+ axes_index: int
795
+ rgb: List[int]
796
+ group_id: Optional[str] = None
797
+ role: str = "standalone"
798
+
799
+ def to_dict(self) -> Dict[str, Any]:
800
+ return asdict(self)
801
+
802
+ @classmethod
803
+ def from_dict(cls, data: Dict[str, Any]) -> "HitRegionEntry":
804
+ return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
805
+
806
+
807
+ @dataclass
808
+ class SelectableRegion:
809
+ """Selectable region for GUI interaction."""
810
+ bbox_px: List[float] # [x0, y0, x1, y1]
811
+ text: Optional[str] = None
812
+ fontsize: Optional[float] = None
813
+ color: Optional[str] = None
814
+
815
+ def to_dict(self) -> Dict[str, Any]:
816
+ result = {"bbox_px": self.bbox_px}
817
+ if self.text:
818
+ result["text"] = self.text
819
+ if self.fontsize:
820
+ result["fontsize"] = self.fontsize
821
+ if self.color:
822
+ result["color"] = self.color
823
+ return result
824
+
825
+ @classmethod
826
+ def from_dict(cls, data: Dict[str, Any]) -> "SelectableRegion":
827
+ return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
828
+
829
+
830
+ @dataclass
831
+ class PlotGeometry:
832
+ """
833
+ Cached geometry data for a rendered plot.
834
+
835
+ Stored in cache/geometry_px.json. This is DERIVED output.
836
+ Can be deleted and regenerated from PlotSpec + PlotStyle.
837
+
838
+ Parameters
839
+ ----------
840
+ source_hash : str
841
+ Hash of spec + style that produced this geometry
842
+ figure_px : tuple
843
+ Rendered figure size (width, height) in pixels
844
+ dpi : int
845
+ DPI used for rendering
846
+ axes : list of RenderedAxes
847
+ Pixel-level axes data
848
+ artists : list of RenderedArtist
849
+ Pixel-level artist data
850
+ hit_regions : dict
851
+ Hit testing data (color_map, groups)
852
+ selectable_regions : dict
853
+ GUI-selectable regions
854
+ """
855
+ source_hash: str
856
+ figure_px: List[int] # [width, height]
857
+ dpi: int
858
+ axes: List[RenderedAxes] = field(default_factory=list)
859
+ artists: List[RenderedArtist] = field(default_factory=list)
860
+ hit_regions: Dict[str, Any] = field(default_factory=dict)
861
+ selectable_regions: Dict[str, Any] = field(default_factory=dict)
862
+ crop_box: Optional[Dict[str, int]] = None
863
+
864
+ # Schema metadata
865
+ scitex_schema: str = "scitex.plt.geometry"
866
+ scitex_schema_version: str = PLOT_GEOMETRY_VERSION
867
+
868
+ def to_dict(self) -> Dict[str, Any]:
869
+ return {
870
+ "schema": {
871
+ "name": self.scitex_schema,
872
+ "version": self.scitex_schema_version,
873
+ },
874
+ "source_hash": self.source_hash,
875
+ "figure_px": self.figure_px,
876
+ "dpi": self.dpi,
877
+ "axes": [ax.to_dict() for ax in self.axes],
878
+ "artists": [ar.to_dict() for ar in self.artists],
879
+ "hit_regions": self.hit_regions,
880
+ "selectable_regions": self.selectable_regions,
881
+ "crop_box": self.crop_box,
882
+ }
883
+
884
+ def to_json(self, indent: int = 2) -> str:
885
+ return json.dumps(self.to_dict(), indent=indent)
886
+
887
+ @classmethod
888
+ def from_dict(cls, data: Dict[str, Any]) -> "PlotGeometry":
889
+ return cls(
890
+ source_hash=data.get("source_hash", ""),
891
+ figure_px=data.get("figure_px", [944, 803]),
892
+ dpi=data.get("dpi", DPI_FALLBACK),
893
+ axes=[RenderedAxes.from_dict(ax) for ax in data.get("axes", [])],
894
+ artists=[RenderedArtist.from_dict(ar) for ar in data.get("artists", [])],
895
+ hit_regions=data.get("hit_regions", {}),
896
+ selectable_regions=data.get("selectable_regions", {}),
897
+ crop_box=data.get("crop_box"),
898
+ )
899
+
900
+ @classmethod
901
+ def from_json(cls, json_str: str) -> "PlotGeometry":
902
+ return cls.from_dict(json.loads(json_str))
903
+
904
+
905
+ # =============================================================================
906
+ # RenderManifest - Render Configuration and Metadata
907
+ # =============================================================================
908
+
909
+
910
+ @dataclass
911
+ class RenderManifest:
912
+ """
913
+ Manifest for rendered outputs.
914
+
915
+ Stored in cache/render_manifest.json.
916
+ Contains metadata about how the render was produced.
917
+ """
918
+ source_hash: str # Hash of spec + style
919
+ panel_size_mm: List[float] # [width, height]
920
+
921
+ # Output files
922
+ overview_png: Optional[str] = None
923
+ overview_svg: Optional[str] = None
924
+ hitmap_png: Optional[str] = None
925
+ hitmap_svg: Optional[str] = None
926
+
927
+ # Render settings
928
+ dpi: int = DPI_FALLBACK # Use scitex.plt.styles.get_default_dpi() for dynamic resolution
929
+ render_px: Optional[List[int]] = None # [width, height]
930
+ crop_margin_mm: float = 1.0
931
+
932
+ # Timestamps
933
+ rendered_at: Optional[str] = None
934
+
935
+ # Schema metadata
936
+ scitex_schema: str = "scitex.plt.render_manifest"
937
+ scitex_schema_version: str = "1.0.0"
938
+
939
+ def __post_init__(self):
940
+ if self.rendered_at is None:
941
+ self.rendered_at = datetime.now().isoformat()
942
+
943
+ def to_dict(self) -> Dict[str, Any]:
944
+ return {
945
+ "schema": {
946
+ "name": self.scitex_schema,
947
+ "version": self.scitex_schema_version,
948
+ },
949
+ "source_hash": self.source_hash,
950
+ "panel_size_mm": self.panel_size_mm,
951
+ "overview_png": self.overview_png,
952
+ "overview_svg": self.overview_svg,
953
+ "hitmap_png": self.hitmap_png,
954
+ "hitmap_svg": self.hitmap_svg,
955
+ "dpi": self.dpi,
956
+ "render_px": self.render_px,
957
+ "crop_margin_mm": self.crop_margin_mm,
958
+ "rendered_at": self.rendered_at,
959
+ }
960
+
961
+ def to_json(self, indent: int = 2) -> str:
962
+ return json.dumps(self.to_dict(), indent=indent)
963
+
964
+ @classmethod
965
+ def from_dict(cls, data: Dict[str, Any]) -> "RenderManifest":
966
+ return cls(**{k: v for k, v in data.items()
967
+ if k in cls.__dataclass_fields__ and k != "scitex_schema" and k != "scitex_schema_version"})
968
+
969
+ @classmethod
970
+ def from_json(cls, json_str: str) -> "RenderManifest":
971
+ return cls.from_dict(json.loads(json_str))
972
+
973
+
974
+ # =============================================================================
975
+ # Public API
976
+ # =============================================================================
977
+
978
+ __all__ = [
979
+ # Version constants
980
+ "PLOT_SPEC_VERSION",
981
+ "PLOT_STYLE_VERSION",
982
+ "PLOT_GEOMETRY_VERSION",
983
+ # Type aliases
984
+ "TraceType",
985
+ "CoordinateSpace",
986
+ "LegendLocation",
987
+ # Bbox classes
988
+ "BboxRatio",
989
+ "BboxPx",
990
+ # Spec classes (canonical)
991
+ "TraceSpec",
992
+ "AxesLimits",
993
+ "AxesLabels",
994
+ "AxesSpecItem",
995
+ "DataSourceSpec",
996
+ "PlotSpec",
997
+ # Style classes
998
+ "TraceStyleSpec",
999
+ "ThemeSpec",
1000
+ "FontSpec",
1001
+ "SizeSpec",
1002
+ "LegendSpec",
1003
+ "PlotStyle",
1004
+ # Geometry classes (cache)
1005
+ "RenderedArtist",
1006
+ "RenderedAxes",
1007
+ "HitRegionEntry",
1008
+ "SelectableRegion",
1009
+ "PlotGeometry",
1010
+ # Manifest
1011
+ "RenderManifest",
1012
+ ]
1013
+
1014
+
1015
+ # EOF