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
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # File: plot_stx_scatter.py - stx_scatter demo
4
+
5
+ """stx_scatter: x, y arrays."""
6
+
7
+ import numpy as np
8
+
9
+
10
+ def plot_stx_scatter(plt, rng, ax=None):
11
+ """stx_scatter - x, y arrays.
12
+
13
+ Demonstrates: ax.stx_scatter()
14
+ """
15
+ if ax is None:
16
+ fig, ax = plt.subplots()
17
+ else:
18
+ fig = ax.get_figure() if hasattr(ax, "get_figure") else ax._fig_scitex
19
+
20
+ x = rng.uniform(0, 10, 50)
21
+ y = 2*x + rng.normal(0, 2, 50)
22
+ ax.stx_scatter(x, y, label='Data')
23
+ ax.set_xyt("X", "Y", "stx_scatter")
24
+ if hasattr(ax, 'legend') and ax.get_legend_handles_labels()[0]:
25
+ ax.legend()
26
+ return fig, ax
27
+
28
+
29
+ # EOF
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # File: plot_stx_shaded_line.py - stx_shaded_line demo
4
+
5
+ """stx_shaded_line: line with shading."""
6
+
7
+ import numpy as np
8
+
9
+
10
+ def plot_stx_shaded_line(plt, rng, ax=None):
11
+ """stx_shaded_line - line with shading.
12
+
13
+ Demonstrates: ax.stx_shaded_line()
14
+ """
15
+ if ax is None:
16
+ fig, ax = plt.subplots()
17
+ else:
18
+ fig = ax.get_figure() if hasattr(ax, "get_figure") else ax._fig_scitex
19
+
20
+ x = np.linspace(0, 10, 100)
21
+ y = np.sin(x)
22
+ ax.stx_shaded_line(x, y, y - 0.2, y + 0.2, label='Shaded')
23
+ ax.set_xyt("X", "Y", "stx_shaded_line")
24
+ if hasattr(ax, 'legend') and ax.get_legend_handles_labels()[0]:
25
+ ax.legend()
26
+ return fig, ax
27
+
28
+
29
+ # EOF
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # File: plot_stx_violin.py - stx_violin demo
4
+
5
+ """stx_violin: list of arrays."""
6
+
7
+ import numpy as np
8
+
9
+
10
+ def plot_stx_violin(plt, rng, ax=None):
11
+ """stx_violin - list of arrays.
12
+
13
+ Demonstrates: ax.stx_violin()
14
+ """
15
+ if ax is None:
16
+ fig, ax = plt.subplots()
17
+ else:
18
+ fig = ax.get_figure() if hasattr(ax, "get_figure") else ax._fig_scitex
19
+
20
+ data = [rng.normal(i, 0.5 + i*0.2, 100) for i in range(4)]
21
+ ax.stx_violin(data, labels=['A', 'B', 'C', 'D'])
22
+ ax.set_xyt("X", "Y", "stx_violin")
23
+ if hasattr(ax, 'legend') and ax.get_legend_handles_labels()[0]:
24
+ ax.legend()
25
+ return fig, ax
26
+
27
+
28
+ # EOF
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # File: plot_stx_violinplot.py - stx_violinplot demo
4
+
5
+ """stx_violinplot: matplotlib violinplot."""
6
+
7
+ import numpy as np
8
+
9
+
10
+ def plot_stx_violinplot(plt, rng, ax=None):
11
+ """stx_violinplot - matplotlib violinplot.
12
+
13
+ Demonstrates: ax.stx_violinplot()
14
+ """
15
+ if ax is None:
16
+ fig, ax = plt.subplots()
17
+ else:
18
+ fig = ax.get_figure() if hasattr(ax, "get_figure") else ax._fig_scitex
19
+
20
+ data = [rng.normal(i, 1, 100) for i in range(4)]
21
+ ax.stx_violinplot(data)
22
+ ax.set_xyt("X", "Y", "stx_violinplot")
23
+ if hasattr(ax, 'legend') and ax.get_legend_handles_labels()[0]:
24
+ ax.legend()
25
+ return fig, ax
26
+
27
+
28
+ # EOF
@@ -0,0 +1,197 @@
1
+ # SciTeX Diagram
2
+
3
+ Paper-optimized diagram generation with semantic constraints.
4
+
5
+ ## Overview
6
+
7
+ SciTeX Diagram provides a **semantic layer** above Mermaid/Graphviz that understands paper constraints:
8
+ - Column width (single/double)
9
+ - Reading direction
10
+ - Node emphasis for scientific communication
11
+ - Automatic splitting of large diagrams
12
+
13
+ **Key insight**: LLMs are good at generating *constraints*, not pixel layouts. SciTeX Diagram defines "what this diagram means for a paper" and compiles that to backend-specific layout directives.
14
+
15
+ ## Architecture
16
+
17
+ ```
18
+ scitex-diagram.yaml ← Semantic layer (human/LLM readable)
19
+
20
+ Compiler (applies paper constraints)
21
+
22
+ workflow.mmd / workflow.dot ← Backend output
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```python
28
+ from scitex.diagram import Diagram
29
+
30
+ # Create programmatically
31
+ d = Diagram(type="workflow", title="Data Pipeline")
32
+ d.add_node("input", "Raw Data", shape="stadium")
33
+ d.add_node("process", "Transform", emphasis="primary")
34
+ d.add_node("output", "Results", shape="stadium")
35
+ d.add_edge("input", "process")
36
+ d.add_edge("process", "output")
37
+
38
+ # Export
39
+ d.to_mermaid("pipeline.mmd")
40
+ d.to_graphviz("pipeline.dot")
41
+ ```
42
+
43
+ ## From YAML Specification
44
+
45
+ ```yaml
46
+ # workflow.diagram.yaml
47
+ type: workflow
48
+ title: SciTeX Figure Lifecycle
49
+
50
+ paper:
51
+ column: single
52
+ mode: publication # draft | publication
53
+ emphasize: [figz_bundle, editor]
54
+ return_edges: # Hide in publication mode
55
+ - [editor, figz_bundle]
56
+
57
+ layout:
58
+ layer_gap: tight
59
+ layers: # rank=same constraints
60
+ - [python, savefig]
61
+ - [figz_bundle]
62
+ - [editor, ai_review]
63
+
64
+ nodes:
65
+ - id: python
66
+ label: Python
67
+ shape: rounded
68
+ - id: figz_bundle
69
+ label: .figz Bundle
70
+ shape: stadium
71
+ emphasis: primary
72
+
73
+ edges:
74
+ - from: python
75
+ to: savefig
76
+ - from: savefig
77
+ to: figz_bundle
78
+ ```
79
+
80
+ ```python
81
+ d = Diagram.from_yaml("workflow.diagram.yaml")
82
+ d.to_mermaid("workflow.mmd")
83
+ ```
84
+
85
+ ## Paper Modes
86
+
87
+ ### Draft Mode (default)
88
+ - Full arrows and labels
89
+ - Medium spacing
90
+ - All edges visible
91
+
92
+ ### Publication Mode
93
+ - Tight spacing (`ranksep=0.3, nodesep=0.2`)
94
+ - Return edges hidden (invisible but constrain layout)
95
+ - No clusters in Graphviz (uses `rank=same` only)
96
+
97
+ ```yaml
98
+ paper:
99
+ mode: publication
100
+ return_edges:
101
+ - [editor, figz_bundle] # Will be invisible
102
+ ```
103
+
104
+ ## Auto-Split Large Diagrams
105
+
106
+ ```python
107
+ d = Diagram.from_yaml("large_workflow.yaml")
108
+
109
+ # Split if > 8 nodes per figure
110
+ parts = d.split(max_nodes=8, strategy="by_groups")
111
+
112
+ for i, part in enumerate(parts):
113
+ part.to_mermaid(f"fig_{chr(65+i)}.mmd") # fig_A.mmd, fig_B.mmd
114
+ ```
115
+
116
+ ### Split Strategies
117
+
118
+ | Strategy | Description |
119
+ |----------|-------------|
120
+ | `by_groups` | Split by layout.groups (deterministic, paper-friendly) |
121
+ | `by_articulation` | Split at hub nodes (graph-theoretic) |
122
+
123
+ Ghost nodes are automatically added at boundaries with `→` prefix.
124
+
125
+ ## Diagram Types
126
+
127
+ | Type | Direction | Use Case |
128
+ |------|-----------|----------|
129
+ | `workflow` | LR | Sequential processes |
130
+ | `decision` | TB | Decision trees |
131
+ | `pipeline` | LR | Data pipelines with stages |
132
+ | `hierarchy` | TB | Tree structures |
133
+ | `comparison` | LR | Side-by-side comparison |
134
+
135
+ ## Node Shapes
136
+
137
+ | Shape | Mermaid | Use Case |
138
+ |-------|---------|----------|
139
+ | `box` | `["label"]` | Default |
140
+ | `rounded` | `("label")` | Processes |
141
+ | `stadium` | `(["label"])` | Start/End |
142
+ | `diamond` | `{"label"}` | Decisions |
143
+ | `circle` | `(("label"))` | Events |
144
+
145
+ ## Emphasis Levels
146
+
147
+ | Level | Color | Use Case |
148
+ |-------|-------|----------|
149
+ | `normal` | Dark | Default |
150
+ | `primary` | Blue | Key nodes |
151
+ | `success` | Green | Positive outcomes |
152
+ | `warning` | Red | Negative outcomes |
153
+ | `muted` | Gray | Secondary/derived |
154
+
155
+ ## Graphviz Output
156
+
157
+ For tightest layouts, use Graphviz:
158
+
159
+ ```bash
160
+ # Render DOT to PNG
161
+ dot -Tpng workflow.dot -o workflow.png
162
+
163
+ # Render DOT to SVG (vector)
164
+ dot -Tsvg workflow.dot -o workflow.svg
165
+ ```
166
+
167
+ Note: Mermaid doesn't support `rank=same` constraints, so Graphviz produces more compact output.
168
+
169
+ ## API Reference
170
+
171
+ ### Diagram Class
172
+
173
+ ```python
174
+ Diagram(type="workflow", title="", column="single")
175
+ Diagram.from_yaml(path)
176
+ Diagram.from_mermaid(path, diagram_type="workflow")
177
+
178
+ diagram.add_node(id, label, shape="box", emphasis="normal")
179
+ diagram.add_edge(source, target, label=None, style="solid")
180
+ diagram.set_group(group_name, node_ids)
181
+ diagram.emphasize(*node_ids)
182
+
183
+ diagram.to_mermaid(path=None) -> str
184
+ diagram.to_graphviz(path=None) -> str
185
+ diagram.to_yaml(path=None) -> str
186
+ diagram.split(max_nodes=12, strategy="by_groups") -> List[Diagram]
187
+ ```
188
+
189
+ ### Schema Classes
190
+
191
+ ```python
192
+ DiagramSpec # Complete specification
193
+ PaperConstraints # column, mode, emphasize, return_edges
194
+ LayoutHints # groups, layers, layer_gap, node_gap
195
+ NodeSpec # id, label, shape, emphasis
196
+ EdgeSpec # source, target, label, style
197
+ ```
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # Timestamp: 2025-12-15
4
+ # Author: ywatanabe / Claude
5
+ # File: scitex/diagram/__init__.py
6
+
7
+ """
8
+ SciTeX Diagram - Paper-optimized diagram generation.
9
+
10
+ This module provides a semantic layer above Mermaid/Graphviz/D2 that
11
+ understands paper constraints (column width, reading direction, emphasis)
12
+ and compiles to backend formats with appropriate layout hints.
13
+
14
+ Key insight: LLMs are good at generating CONSTRAINTS, not pixel layouts.
15
+ SciTeX Diagram defines "what this diagram means for a paper" and compiles
16
+ that to backend-specific layout directives.
17
+
18
+ Example
19
+ -------
20
+ >>> from scitex.diagram import Diagram
21
+ >>>
22
+ >>> diagram = Diagram.from_yaml("workflow.diagram.yaml")
23
+ >>> diagram.to_mermaid("workflow.mmd")
24
+ >>> diagram.to_graphviz("workflow.dot")
25
+ """
26
+
27
+ from scitex.diagram._schema import DiagramSpec, PaperConstraints, LayoutHints, PaperMode
28
+ from scitex.diagram._diagram import Diagram
29
+ from scitex.diagram._compile import compile_to_mermaid, compile_to_graphviz
30
+ from scitex.diagram._presets import WORKFLOW_PRESET, DECISION_PRESET, PIPELINE_PRESET
31
+ from scitex.diagram._split import split_diagram, SplitConfig, SplitStrategy, SplitResult
32
+
33
+ __all__ = [
34
+ "Diagram",
35
+ "DiagramSpec",
36
+ "PaperConstraints",
37
+ "LayoutHints",
38
+ "PaperMode",
39
+ "compile_to_mermaid",
40
+ "compile_to_graphviz",
41
+ "WORKFLOW_PRESET",
42
+ "DECISION_PRESET",
43
+ "PIPELINE_PRESET",
44
+ "split_diagram",
45
+ "SplitConfig",
46
+ "SplitStrategy",
47
+ "SplitResult",
48
+ ]
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # Timestamp: 2025-12-15
4
+ # Author: ywatanabe / Claude
5
+ # File: scitex/diagram/_compile.py
6
+
7
+ """
8
+ Compilers from DiagramSpec to backend formats (Mermaid, Graphviz).
9
+
10
+ The compiler applies paper constraints to generate backend-specific
11
+ layout directives. This is where domain knowledge about "good paper figures"
12
+ gets encoded.
13
+ """
14
+
15
+ import json
16
+ from typing import Optional
17
+ from scitex.diagram._schema import (
18
+ DiagramSpec, DiagramType, ColumnLayout, SpacingLevel, PaperMode
19
+ )
20
+ from scitex.diagram._presets import get_preset, DiagramPreset
21
+
22
+
23
+ def compile_to_mermaid(
24
+ spec: DiagramSpec,
25
+ preset: Optional[DiagramPreset] = None
26
+ ) -> str:
27
+ """
28
+ Compile DiagramSpec to Mermaid format with paper-optimized settings.
29
+
30
+ Parameters
31
+ ----------
32
+ spec : DiagramSpec
33
+ The semantic diagram specification.
34
+ preset : DiagramPreset, optional
35
+ Override preset (default: inferred from spec.type).
36
+
37
+ Returns
38
+ -------
39
+ str
40
+ Mermaid diagram source code.
41
+ """
42
+ if preset is None:
43
+ preset = get_preset(spec.type.value)
44
+
45
+ lines = []
46
+
47
+ # Theme initialization
48
+ theme_vars = {**preset.mermaid_theme, **spec.theme}
49
+ theme_json = json.dumps({"theme": "base", "themeVariables": theme_vars})
50
+ lines.append(f"%%{{init: {theme_json}}}%%")
51
+
52
+ # Determine direction based on paper constraints
53
+ direction = preset.mermaid_direction
54
+ if spec.paper.reading_direction == "top_to_bottom":
55
+ direction = "TB"
56
+ elif spec.paper.column == ColumnLayout.DOUBLE:
57
+ # Double column prefers vertical to save horizontal space
58
+ direction = "TB"
59
+
60
+ lines.append(f"graph {direction}")
61
+
62
+ # Build node ID to spec mapping
63
+ node_map = {n.id: n for n in spec.nodes}
64
+
65
+ # Generate subgraphs for groups
66
+ indent = " "
67
+ for group_name, group_nodes in spec.layout.groups.items():
68
+ lines.append(f'{indent}subgraph {_sanitize_id(group_name)}["{group_name}"]')
69
+ for node_id in group_nodes:
70
+ if node_id in node_map:
71
+ node = node_map[node_id]
72
+ lines.append(f"{indent}{indent}{_mermaid_node(node, preset)}")
73
+ lines.append(f"{indent}end")
74
+
75
+ # Generate standalone nodes (not in any group)
76
+ grouped_nodes = set()
77
+ for group_nodes in spec.layout.groups.values():
78
+ grouped_nodes.update(group_nodes)
79
+
80
+ for node in spec.nodes:
81
+ if node.id not in grouped_nodes:
82
+ lines.append(f"{indent}{_mermaid_node(node, preset)}")
83
+
84
+ # Generate edges
85
+ for edge in spec.edges:
86
+ edge_str = _mermaid_edge(edge)
87
+ lines.append(f"{indent}{edge_str}")
88
+
89
+ # Generate styles for emphasized nodes
90
+ for node in spec.nodes:
91
+ if node.emphasis != "normal" or node.id in spec.paper.emphasize:
92
+ emphasis = "primary" if node.id in spec.paper.emphasize else node.emphasis
93
+ style = preset.emphasis_styles.get(emphasis, {})
94
+ if style:
95
+ style_parts = [f"{k}:{v}" for k, v in style.items()]
96
+ lines.append(f"{indent}style {_sanitize_id(node.id)} {','.join(style_parts)}")
97
+
98
+ return "\n".join(lines)
99
+
100
+
101
+ def compile_to_graphviz(
102
+ spec: DiagramSpec,
103
+ preset: Optional[DiagramPreset] = None
104
+ ) -> str:
105
+ """
106
+ Compile DiagramSpec to Graphviz DOT format.
107
+
108
+ Parameters
109
+ ----------
110
+ spec : DiagramSpec
111
+ The semantic diagram specification.
112
+ preset : DiagramPreset, optional
113
+ Override preset.
114
+
115
+ Returns
116
+ -------
117
+ str
118
+ Graphviz DOT source code.
119
+ """
120
+ if preset is None:
121
+ preset = get_preset(spec.type.value)
122
+
123
+ is_publication = spec.paper.mode == PaperMode.PUBLICATION
124
+ lines = []
125
+
126
+ # Determine direction
127
+ rankdir = preset.graphviz_rankdir
128
+ if spec.paper.reading_direction == "top_to_bottom":
129
+ rankdir = "TB"
130
+ elif spec.paper.column == ColumnLayout.DOUBLE:
131
+ rankdir = "TB"
132
+
133
+ # Get spacing - publication mode uses tight spacing
134
+ if is_publication:
135
+ spacing = preset.spacing_map.get("tight", {})
136
+ else:
137
+ spacing = preset.spacing_map.get(spec.layout.layer_gap.value, {})
138
+ ranksep = spacing.get("ranksep", preset.graphviz_ranksep)
139
+ nodesep = spacing.get("nodesep", preset.graphviz_nodesep)
140
+
141
+ lines.append("digraph G {")
142
+ lines.append(f" rankdir={rankdir};")
143
+ lines.append(f" ranksep={ranksep};")
144
+ lines.append(f" nodesep={nodesep};")
145
+ lines.append(" splines=ortho;") # Orthogonal edges for cleaner look
146
+ lines.append(' node [fontname="Helvetica", fontsize=10];')
147
+ lines.append(' edge [fontname="Helvetica", fontsize=9];')
148
+ lines.append("")
149
+
150
+ # Node map
151
+ node_map = {n.id: n for n in spec.nodes}
152
+
153
+ # Build return edges set for publication mode
154
+ return_edge_set = set()
155
+ for e in spec.paper.return_edges:
156
+ if len(e) >= 2:
157
+ return_edge_set.add((e[0], e[1]))
158
+
159
+ # Generate subgraphs (without clusters for tighter layout in publication)
160
+ if is_publication and spec.layout.layers:
161
+ # In publication mode with layers, skip clusters - use rank=same instead
162
+ for node in spec.nodes:
163
+ lines.append(f" {_graphviz_node(node, preset, spec.paper.emphasize)}")
164
+ else:
165
+ # Draft mode: use clusters for visual grouping
166
+ cluster_idx = 0
167
+ for group_name, group_nodes in spec.layout.groups.items():
168
+ lines.append(f' subgraph cluster_{cluster_idx} {{')
169
+ lines.append(f' label="{group_name}";')
170
+ for node_id in group_nodes:
171
+ if node_id in node_map:
172
+ node = node_map[node_id]
173
+ lines.append(f" {_graphviz_node(node, preset, spec.paper.emphasize)}")
174
+ lines.append(" }")
175
+ cluster_idx += 1
176
+
177
+ # Standalone nodes
178
+ grouped_nodes = set()
179
+ for group_nodes in spec.layout.groups.values():
180
+ grouped_nodes.update(group_nodes)
181
+
182
+ for node in spec.nodes:
183
+ if node.id not in grouped_nodes:
184
+ lines.append(f" {_graphviz_node(node, preset, spec.paper.emphasize)}")
185
+
186
+ lines.append("")
187
+
188
+ # Rank constraints from layers (CRITICAL for minimizing whitespace)
189
+ for layer in spec.layout.layers:
190
+ if layer:
191
+ node_ids = "; ".join(_sanitize_id(n) for n in layer)
192
+ lines.append(f" {{ rank=same; {node_ids}; }}")
193
+
194
+ lines.append("")
195
+
196
+ # Edges - handle return edges in publication mode
197
+ for edge in spec.edges:
198
+ edge_key = (edge.source, edge.target)
199
+ if is_publication and edge_key in return_edge_set:
200
+ # Make return edges invisible in publication mode
201
+ lines.append(f" {_graphviz_edge_with_style(edge, invisible=True)}")
202
+ else:
203
+ lines.append(f" {_graphviz_edge(edge)}")
204
+
205
+ lines.append("}")
206
+
207
+ return "\n".join(lines)
208
+
209
+
210
+ def _sanitize_id(s: str) -> str:
211
+ """Make string safe for use as node ID."""
212
+ import re
213
+ # Remove or replace problematic characters for Mermaid/Graphviz
214
+ s = re.sub(r'[^\w]', '_', s) # Replace non-word chars with _
215
+ s = re.sub(r'_+', '_', s) # Collapse multiple underscores
216
+ s = s.strip('_') # Remove leading/trailing underscores
217
+ return s or "node"
218
+
219
+
220
+ def _mermaid_node(node, preset: DiagramPreset) -> str:
221
+ """Generate Mermaid node definition."""
222
+ shape_template = preset.mermaid_shapes.get(node.shape, '["__LABEL__"]')
223
+ shape_str = shape_template.replace("__LABEL__", node.label)
224
+ return f"{_sanitize_id(node.id)}{shape_str}"
225
+
226
+
227
+ def _mermaid_edge(edge) -> str:
228
+ """Generate Mermaid edge definition."""
229
+ arrow = "-->" if edge.arrow == "normal" else "---"
230
+ if edge.style == "dashed":
231
+ arrow = "-.->" if edge.arrow == "normal" else "-.-"
232
+ elif edge.style == "dotted":
233
+ arrow = "..>" if edge.arrow == "normal" else "..."
234
+
235
+ src = _sanitize_id(edge.source)
236
+ tgt = _sanitize_id(edge.target)
237
+
238
+ if edge.label:
239
+ return f'{src} {arrow}|"{edge.label}"| {tgt}'
240
+ return f"{src} {arrow} {tgt}"
241
+
242
+
243
+ def _graphviz_node(node, preset: DiagramPreset, emphasize: list) -> str:
244
+ """Generate Graphviz node definition."""
245
+ shape = preset.graphviz_shapes.get(node.shape, "box")
246
+
247
+ # Get emphasis style
248
+ emphasis_key = "primary" if node.id in emphasize else node.emphasis
249
+ style = preset.emphasis_styles.get(emphasis_key, {})
250
+
251
+ attrs = [f'label="{node.label}"', f'shape={shape}']
252
+
253
+ # Collect style values (filled, rounded, etc.) - combine with comma
254
+ styles = []
255
+ if style.get("fill"):
256
+ attrs.append(f'fillcolor="{style["fill"]}"')
257
+ styles.append("filled")
258
+ if style.get("stroke"):
259
+ attrs.append(f'color="{style["stroke"]}"')
260
+ if node.shape == "rounded":
261
+ styles.append("rounded")
262
+
263
+ # Output style once with comma-separated values
264
+ if styles:
265
+ attrs.append(f'style="{",".join(styles)}"')
266
+
267
+ return f'{_sanitize_id(node.id)} [{", ".join(attrs)}];'
268
+
269
+
270
+ def _graphviz_edge(edge) -> str:
271
+ """Generate Graphviz edge definition."""
272
+ src = _sanitize_id(edge.source)
273
+ tgt = _sanitize_id(edge.target)
274
+
275
+ attrs = []
276
+ if edge.label:
277
+ attrs.append(f'label="{edge.label}"')
278
+ if edge.style == "dashed":
279
+ attrs.append("style=dashed")
280
+ elif edge.style == "dotted":
281
+ attrs.append("style=dotted")
282
+ if edge.arrow == "none":
283
+ attrs.append("arrowhead=none")
284
+
285
+ if attrs:
286
+ return f'{src} -> {tgt} [{", ".join(attrs)}];'
287
+ return f"{src} -> {tgt};"
288
+
289
+
290
+ def _graphviz_edge_with_style(edge, invisible: bool = False) -> str:
291
+ """Generate Graphviz edge with optional invisible style."""
292
+ src = _sanitize_id(edge.source)
293
+ tgt = _sanitize_id(edge.target)
294
+
295
+ attrs = []
296
+ if invisible:
297
+ attrs.append("style=invis")
298
+ # Invisible edges still constrain layout
299
+ attrs.append("constraint=true")
300
+ else:
301
+ if edge.label:
302
+ attrs.append(f'label="{edge.label}"')
303
+ if edge.style == "dashed":
304
+ attrs.append("style=dashed")
305
+ elif edge.style == "dotted":
306
+ attrs.append("style=dotted")
307
+ if edge.arrow == "none":
308
+ attrs.append("arrowhead=none")
309
+
310
+ if attrs:
311
+ return f'{src} -> {tgt} [{", ".join(attrs)}];'
312
+ return f"{src} -> {tgt};"