scitex 2.14.0__py3-none-any.whl → 2.15.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. scitex/__init__.py +71 -17
  2. scitex/_env_loader.py +156 -0
  3. scitex/_mcp_resources/__init__.py +37 -0
  4. scitex/_mcp_resources/_cheatsheet.py +135 -0
  5. scitex/_mcp_resources/_figrecipe.py +138 -0
  6. scitex/_mcp_resources/_formats.py +102 -0
  7. scitex/_mcp_resources/_modules.py +337 -0
  8. scitex/_mcp_resources/_session.py +149 -0
  9. scitex/_mcp_tools/__init__.py +4 -0
  10. scitex/_mcp_tools/audio.py +66 -0
  11. scitex/_mcp_tools/diagram.py +11 -95
  12. scitex/_mcp_tools/introspect.py +210 -0
  13. scitex/_mcp_tools/plt.py +260 -305
  14. scitex/_mcp_tools/scholar.py +74 -0
  15. scitex/_mcp_tools/social.py +244 -0
  16. scitex/_mcp_tools/template.py +24 -0
  17. scitex/_mcp_tools/writer.py +21 -204
  18. scitex/ai/_gen_ai/_PARAMS.py +10 -7
  19. scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
  20. scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
  21. scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
  22. scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
  23. scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
  24. scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
  25. scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
  26. scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
  27. scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
  28. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
  29. scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
  30. scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
  31. scitex/audio/README.md +40 -36
  32. scitex/audio/__init__.py +129 -61
  33. scitex/audio/_branding.py +185 -0
  34. scitex/audio/_mcp/__init__.py +32 -0
  35. scitex/audio/_mcp/handlers.py +59 -6
  36. scitex/audio/_mcp/speak_handlers.py +238 -0
  37. scitex/audio/_relay.py +225 -0
  38. scitex/audio/_tts.py +18 -10
  39. scitex/audio/engines/base.py +17 -10
  40. scitex/audio/engines/elevenlabs_engine.py +7 -2
  41. scitex/audio/mcp_server.py +228 -75
  42. scitex/canvas/README.md +1 -1
  43. scitex/canvas/editor/_dearpygui/__init__.py +25 -0
  44. scitex/canvas/editor/_dearpygui/_editor.py +147 -0
  45. scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
  46. scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
  47. scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
  48. scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
  49. scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
  50. scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
  51. scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
  52. scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
  53. scitex/canvas/editor/_dearpygui/_selection.py +295 -0
  54. scitex/canvas/editor/_dearpygui/_state.py +93 -0
  55. scitex/canvas/editor/_dearpygui/_utils.py +61 -0
  56. scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
  57. scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
  58. scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
  59. scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
  60. scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
  61. scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
  62. scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
  63. scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
  64. scitex/canvas/editor/flask_editor/_core.py +25 -1684
  65. scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
  66. scitex/cli/__init__.py +38 -43
  67. scitex/cli/audio.py +76 -27
  68. scitex/cli/capture.py +13 -20
  69. scitex/cli/introspect.py +481 -0
  70. scitex/cli/main.py +200 -109
  71. scitex/cli/mcp.py +60 -34
  72. scitex/cli/plt.py +357 -0
  73. scitex/cli/repro.py +15 -8
  74. scitex/cli/resource.py +15 -8
  75. scitex/cli/scholar/__init__.py +23 -8
  76. scitex/cli/scholar/_crossref_scitex.py +296 -0
  77. scitex/cli/scholar/_fetch.py +25 -3
  78. scitex/cli/social.py +314 -0
  79. scitex/cli/stats.py +15 -8
  80. scitex/cli/template.py +129 -12
  81. scitex/cli/tex.py +15 -8
  82. scitex/cli/writer.py +132 -8
  83. scitex/cloud/__init__.py +41 -2
  84. scitex/config/README.md +1 -1
  85. scitex/config/__init__.py +16 -2
  86. scitex/config/_env_registry.py +256 -0
  87. scitex/context/__init__.py +22 -0
  88. scitex/dev/__init__.py +20 -1
  89. scitex/diagram/__init__.py +42 -19
  90. scitex/diagram/mcp_server.py +13 -125
  91. scitex/gen/__init__.py +50 -14
  92. scitex/gen/_list_packages.py +4 -4
  93. scitex/introspect/__init__.py +82 -0
  94. scitex/introspect/_call_graph.py +303 -0
  95. scitex/introspect/_class_hierarchy.py +163 -0
  96. scitex/introspect/_core.py +41 -0
  97. scitex/introspect/_docstring.py +131 -0
  98. scitex/introspect/_examples.py +113 -0
  99. scitex/introspect/_imports.py +271 -0
  100. scitex/{gen/_inspect_module.py → introspect/_list_api.py} +43 -54
  101. scitex/introspect/_mcp/__init__.py +41 -0
  102. scitex/introspect/_mcp/handlers.py +233 -0
  103. scitex/introspect/_members.py +155 -0
  104. scitex/introspect/_resolve.py +89 -0
  105. scitex/introspect/_signature.py +131 -0
  106. scitex/introspect/_source.py +80 -0
  107. scitex/introspect/_type_hints.py +172 -0
  108. scitex/io/_save.py +1 -2
  109. scitex/io/bundle/README.md +1 -1
  110. scitex/logging/_formatters.py +19 -9
  111. scitex/mcp_server.py +98 -5
  112. scitex/os/__init__.py +4 -0
  113. scitex/{gen → os}/_check_host.py +4 -5
  114. scitex/plt/__init__.py +245 -550
  115. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
  116. scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  117. scitex/plt/gallery/README.md +1 -1
  118. scitex/plt/utils/_hitmap/__init__.py +82 -0
  119. scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
  120. scitex/plt/utils/_hitmap/_color_application.py +346 -0
  121. scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
  122. scitex/plt/utils/_hitmap/_constants.py +40 -0
  123. scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
  124. scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
  125. scitex/plt/utils/_hitmap/_query.py +113 -0
  126. scitex/plt/utils/_hitmap.py +46 -1616
  127. scitex/plt/utils/_metadata/__init__.py +80 -0
  128. scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
  129. scitex/plt/utils/_metadata/_artists/_base.py +195 -0
  130. scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
  131. scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
  132. scitex/plt/utils/_metadata/_artists/_images.py +80 -0
  133. scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
  134. scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
  135. scitex/plt/utils/_metadata/_artists/_text.py +106 -0
  136. scitex/plt/utils/_metadata/_csv.py +416 -0
  137. scitex/plt/utils/_metadata/_detect.py +225 -0
  138. scitex/plt/utils/_metadata/_legend.py +127 -0
  139. scitex/plt/utils/_metadata/_rounding.py +117 -0
  140. scitex/plt/utils/_metadata/_verification.py +202 -0
  141. scitex/schema/README.md +1 -1
  142. scitex/scholar/__init__.py +8 -0
  143. scitex/scholar/_mcp/crossref_handlers.py +265 -0
  144. scitex/scholar/core/Scholar.py +63 -1700
  145. scitex/scholar/core/_mixins/__init__.py +36 -0
  146. scitex/scholar/core/_mixins/_enrichers.py +270 -0
  147. scitex/scholar/core/_mixins/_library_handlers.py +100 -0
  148. scitex/scholar/core/_mixins/_loaders.py +103 -0
  149. scitex/scholar/core/_mixins/_pdf_download.py +375 -0
  150. scitex/scholar/core/_mixins/_pipeline.py +312 -0
  151. scitex/scholar/core/_mixins/_project_handlers.py +125 -0
  152. scitex/scholar/core/_mixins/_savers.py +69 -0
  153. scitex/scholar/core/_mixins/_search.py +103 -0
  154. scitex/scholar/core/_mixins/_services.py +88 -0
  155. scitex/scholar/core/_mixins/_url_finding.py +105 -0
  156. scitex/scholar/crossref_scitex.py +367 -0
  157. scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  158. scitex/scholar/examples/00_run_all.sh +120 -0
  159. scitex/scholar/jobs/_executors.py +27 -3
  160. scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
  161. scitex/scholar/pdf_download/_cli.py +154 -0
  162. scitex/scholar/pdf_download/strategies/__init__.py +11 -8
  163. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
  164. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
  165. scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
  166. scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
  167. scitex/scholar/pipelines/_single_steps.py +71 -36
  168. scitex/scholar/storage/_LibraryManager.py +97 -1695
  169. scitex/scholar/storage/_mixins/__init__.py +30 -0
  170. scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
  171. scitex/scholar/storage/_mixins/_library_operations.py +218 -0
  172. scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
  173. scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
  174. scitex/scholar/storage/_mixins/_resolution.py +376 -0
  175. scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
  176. scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
  177. scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
  178. scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
  179. scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
  180. scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
  181. scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
  182. scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
  183. scitex/security/README.md +3 -3
  184. scitex/session/README.md +1 -1
  185. scitex/session/__init__.py +26 -7
  186. scitex/session/_decorator.py +1 -1
  187. scitex/sh/README.md +1 -1
  188. scitex/sh/__init__.py +7 -4
  189. scitex/social/__init__.py +155 -0
  190. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  191. scitex/stats/_mcp/_handlers/__init__.py +31 -0
  192. scitex/stats/_mcp/_handlers/_corrections.py +113 -0
  193. scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
  194. scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
  195. scitex/stats/_mcp/_handlers/_format.py +94 -0
  196. scitex/stats/_mcp/_handlers/_normality.py +110 -0
  197. scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
  198. scitex/stats/_mcp/_handlers/_power.py +247 -0
  199. scitex/stats/_mcp/_handlers/_recommend.py +102 -0
  200. scitex/stats/_mcp/_handlers/_run_test.py +279 -0
  201. scitex/stats/_mcp/_handlers/_stars.py +48 -0
  202. scitex/stats/_mcp/handlers.py +19 -1171
  203. scitex/stats/auto/_stat_style.py +175 -0
  204. scitex/stats/auto/_style_definitions.py +411 -0
  205. scitex/stats/auto/_styles.py +22 -620
  206. scitex/stats/descriptive/__init__.py +11 -8
  207. scitex/stats/descriptive/_ci.py +39 -0
  208. scitex/stats/power/_power.py +15 -4
  209. scitex/str/__init__.py +2 -1
  210. scitex/str/_title_case.py +63 -0
  211. scitex/template/README.md +1 -1
  212. scitex/template/__init__.py +25 -10
  213. scitex/template/_code_templates.py +147 -0
  214. scitex/template/_mcp/handlers.py +81 -0
  215. scitex/template/_mcp/tool_schemas.py +55 -0
  216. scitex/template/_templates/__init__.py +51 -0
  217. scitex/template/_templates/audio.py +233 -0
  218. scitex/template/_templates/canvas.py +312 -0
  219. scitex/template/_templates/capture.py +268 -0
  220. scitex/template/_templates/config.py +43 -0
  221. scitex/template/_templates/diagram.py +294 -0
  222. scitex/template/_templates/io.py +107 -0
  223. scitex/template/_templates/module.py +53 -0
  224. scitex/template/_templates/plt.py +202 -0
  225. scitex/template/_templates/scholar.py +267 -0
  226. scitex/template/_templates/session.py +130 -0
  227. scitex/template/_templates/session_minimal.py +43 -0
  228. scitex/template/_templates/session_plot.py +67 -0
  229. scitex/template/_templates/session_stats.py +77 -0
  230. scitex/template/_templates/stats.py +323 -0
  231. scitex/template/_templates/writer.py +296 -0
  232. scitex/template/clone_writer_directory.py +5 -5
  233. scitex/ui/_backends/_email.py +10 -2
  234. scitex/ui/_backends/_webhook.py +5 -1
  235. scitex/web/_search_pubmed.py +10 -6
  236. scitex/writer/README.md +1 -1
  237. scitex/writer/_mcp/handlers.py +11 -744
  238. scitex/writer/_mcp/tool_schemas.py +5 -335
  239. scitex-2.15.2.dist-info/METADATA +648 -0
  240. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/RECORD +246 -150
  241. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  242. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  243. scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
  244. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
  245. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
  246. scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
  247. scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
  248. scitex/diagram/_compile.py +0 -312
  249. scitex/diagram/_diagram.py +0 -355
  250. scitex/diagram/_mcp/__init__.py +0 -4
  251. scitex/diagram/_mcp/handlers.py +0 -400
  252. scitex/diagram/_mcp/tool_schemas.py +0 -157
  253. scitex/diagram/_presets.py +0 -173
  254. scitex/diagram/_schema.py +0 -182
  255. scitex/diagram/_split.py +0 -278
  256. scitex/gen/_ci.py +0 -12
  257. scitex/gen/_title_case.py +0 -89
  258. scitex/plt/_mcp/__init__.py +0 -4
  259. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  260. scitex/plt/_mcp/_handlers_figure.py +0 -195
  261. scitex/plt/_mcp/_handlers_plot.py +0 -252
  262. scitex/plt/_mcp/_handlers_style.py +0 -219
  263. scitex/plt/_mcp/handlers.py +0 -74
  264. scitex/plt/_mcp/tool_schemas.py +0 -497
  265. scitex/plt/mcp_server.py +0 -231
  266. scitex/scholar/data/.gitkeep +0 -0
  267. scitex/scholar/data/README.md +0 -44
  268. scitex/scholar/data/bib_files/bibliography.bib +0 -1952
  269. scitex/scholar/data/bib_files/neurovista.bib +0 -277
  270. scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
  271. scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
  272. scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
  273. scitex/scholar/data/bib_files/openaccess.bib +0 -89
  274. scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
  275. scitex/scholar/data/bib_files/pac.bib +0 -698
  276. scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
  277. scitex/scholar/data/bib_files/pac_processed.bib +0 -0
  278. scitex/scholar/data/bib_files/pac_titles.txt +0 -75
  279. scitex/scholar/data/bib_files/paywalled.bib +0 -98
  280. scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
  281. scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
  282. scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
  283. scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
  284. scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
  285. scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
  286. scitex/scholar/data/bib_files/test_seizure.bib +0 -46
  287. scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
  288. scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
  289. scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
  290. scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
  291. scitex/scholar/data/impact_factor.db +0 -0
  292. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  293. scitex/scholar/examples/dev.py +0 -38
  294. scitex-2.14.0.dist-info/METADATA +0 -1238
  295. /scitex/{gen → context}/_detect_environment.py +0 -0
  296. /scitex/{gen → context}/_get_notebook_path.py +0 -0
  297. /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
  298. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/WHEEL +0 -0
  299. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/entry_points.txt +0 -0
  300. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,476 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/canvas/editor/_dearpygui/_handlers.py
4
+
5
+ """
6
+ Event handlers for DearPyGui editor.
7
+
8
+ Handles all callbacks and user interactions.
9
+ """
10
+
11
+ import copy
12
+ import time
13
+ from typing import TYPE_CHECKING
14
+
15
+ if TYPE_CHECKING:
16
+ from ._state import EditorState
17
+
18
+
19
+ def on_value_change(state: "EditorState", dpg) -> None:
20
+ """Handle value changes from widgets."""
21
+ from ._rendering import update_preview
22
+
23
+ state.user_modified = True
24
+ collect_overrides(state, dpg)
25
+ update_preview(state, dpg)
26
+
27
+
28
+ def collect_overrides(state: "EditorState", dpg) -> None:
29
+ """Collect current values from all widgets."""
30
+ o = state.current_overrides
31
+
32
+ # Labels
33
+ o["title"] = dpg.get_value("title_input")
34
+ o["xlabel"] = dpg.get_value("xlabel_input")
35
+ o["ylabel"] = dpg.get_value("ylabel_input")
36
+
37
+ # Line style
38
+ o["linewidth"] = dpg.get_value("linewidth_slider")
39
+
40
+ # Font settings
41
+ o["title_fontsize"] = dpg.get_value("title_fontsize_slider")
42
+ o["axis_fontsize"] = dpg.get_value("axis_fontsize_slider")
43
+ o["tick_fontsize"] = dpg.get_value("tick_fontsize_slider")
44
+ o["legend_fontsize"] = dpg.get_value("legend_fontsize_slider")
45
+
46
+ # Tick settings
47
+ o["n_ticks"] = dpg.get_value("n_ticks_slider")
48
+ o["tick_length"] = dpg.get_value("tick_length_slider")
49
+ o["tick_width"] = dpg.get_value("tick_width_slider")
50
+ o["tick_direction"] = dpg.get_value("tick_direction_combo")
51
+
52
+ # Style
53
+ o["grid"] = dpg.get_value("grid_checkbox")
54
+ o["hide_top_spine"] = dpg.get_value("hide_top_spine_checkbox")
55
+ o["hide_right_spine"] = dpg.get_value("hide_right_spine_checkbox")
56
+ o["transparent"] = dpg.get_value("transparent_checkbox")
57
+ o["axis_width"] = dpg.get_value("axis_width_slider")
58
+
59
+ # Legend
60
+ o["legend_visible"] = dpg.get_value("legend_visible_checkbox")
61
+ o["legend_frameon"] = dpg.get_value("legend_frameon_checkbox")
62
+ o["legend_loc"] = dpg.get_value("legend_loc_combo")
63
+
64
+ # Dimensions
65
+ o["fig_size"] = [
66
+ dpg.get_value("fig_width_input"),
67
+ dpg.get_value("fig_height_input"),
68
+ ]
69
+ o["dpi"] = dpg.get_value("dpi_slider")
70
+
71
+
72
+ def on_apply_limits(state: "EditorState", dpg) -> None:
73
+ """Apply axis limits."""
74
+ from ._rendering import update_preview
75
+
76
+ xmin = dpg.get_value("xmin_input")
77
+ xmax = dpg.get_value("xmax_input")
78
+ ymin = dpg.get_value("ymin_input")
79
+ ymax = dpg.get_value("ymax_input")
80
+
81
+ if xmin < xmax:
82
+ state.current_overrides["xlim"] = [xmin, xmax]
83
+ if ymin < ymax:
84
+ state.current_overrides["ylim"] = [ymin, ymax]
85
+
86
+ state.user_modified = True
87
+ update_preview(state, dpg)
88
+
89
+
90
+ def on_add_annotation(state: "EditorState", dpg) -> None:
91
+ """Add text annotation."""
92
+ from ._rendering import update_preview
93
+
94
+ text = dpg.get_value("annot_text_input")
95
+ if not text:
96
+ return
97
+
98
+ x = dpg.get_value("annot_x_input")
99
+ y = dpg.get_value("annot_y_input")
100
+
101
+ if "annotations" not in state.current_overrides:
102
+ state.current_overrides["annotations"] = []
103
+
104
+ state.current_overrides["annotations"].append(
105
+ {
106
+ "type": "text",
107
+ "text": text,
108
+ "x": x,
109
+ "y": y,
110
+ "fontsize": state.current_overrides.get("axis_fontsize", 7),
111
+ }
112
+ )
113
+
114
+ dpg.set_value("annot_text_input", "")
115
+ update_annotations_list(state, dpg)
116
+ state.user_modified = True
117
+ update_preview(state, dpg)
118
+
119
+
120
+ def on_remove_annotation(state: "EditorState", dpg) -> None:
121
+ """Remove selected annotation."""
122
+ from ._rendering import update_preview
123
+
124
+ selected = dpg.get_value("annotations_listbox")
125
+ annotations = state.current_overrides.get("annotations", [])
126
+
127
+ if selected and annotations:
128
+ # Find index by text
129
+ for i, ann in enumerate(annotations):
130
+ label = (
131
+ f"{ann.get('text', '')[:20]} "
132
+ f"({ann.get('x', 0):.2f}, {ann.get('y', 0):.2f})"
133
+ )
134
+ if label == selected:
135
+ del annotations[i]
136
+ break
137
+
138
+ update_annotations_list(state, dpg)
139
+ state.user_modified = True
140
+ update_preview(state, dpg)
141
+
142
+
143
+ def update_annotations_list(state: "EditorState", dpg) -> None:
144
+ """Update the annotations listbox."""
145
+ annotations = state.current_overrides.get("annotations", [])
146
+ items = []
147
+ for ann in annotations:
148
+ if ann.get("type") == "text":
149
+ label = (
150
+ f"{ann.get('text', '')[:20]} "
151
+ f"({ann.get('x', 0):.2f}, {ann.get('y', 0):.2f})"
152
+ )
153
+ items.append(label)
154
+
155
+ dpg.configure_item("annotations_listbox", items=items)
156
+
157
+
158
+ def on_preview_click(state: "EditorState", dpg, app_data) -> None:
159
+ """Handle click on preview image to select element."""
160
+ from ._selection import find_clicked_element, find_nearest_trace, select_element
161
+
162
+ # Only handle left clicks
163
+ if app_data != 0: # 0 = left button
164
+ return
165
+
166
+ # Get mouse position relative to viewport
167
+ mouse_pos = dpg.get_mouse_pos(local=False)
168
+
169
+ # Get preview image position and size
170
+ if not dpg.does_item_exist("preview_image"):
171
+ return
172
+
173
+ # Get the image item's position in the window
174
+ img_pos = dpg.get_item_pos("preview_image")
175
+ panel_pos = dpg.get_item_pos("preview_panel")
176
+
177
+ # Calculate click position relative to image
178
+ click_x = mouse_pos[0] - panel_pos[0] - img_pos[0]
179
+ click_y = mouse_pos[1] - panel_pos[1] - img_pos[1]
180
+
181
+ # Check if click is within image bounds (800x600)
182
+ max_width, max_height = 800, 600
183
+ if not (0 <= click_x <= max_width and 0 <= click_y <= max_height):
184
+ return
185
+
186
+ # First check if click is on a fixed element (title, labels, axes, legend)
187
+ element = find_clicked_element(state, click_x, click_y)
188
+
189
+ if element:
190
+ select_element(state, element, dpg)
191
+ else:
192
+ # Fall back to trace selection
193
+ trace_idx = find_nearest_trace(state, click_x, click_y, max_width, max_height)
194
+ if trace_idx is not None:
195
+ select_element(state, {"type": "trace", "index": trace_idx}, dpg)
196
+
197
+
198
+ def on_preview_hover(state: "EditorState", dpg, app_data) -> None:
199
+ """Handle mouse move for hover effects on preview (optimized with caching)."""
200
+ from ._rendering import update_hover_overlay
201
+ from ._selection import find_clicked_element, find_nearest_trace
202
+
203
+ # Throttle hover updates - reduced to 16ms (~60fps) since we use fast overlay
204
+ current_time = time.time()
205
+ if current_time - state.last_hover_check < 0.016:
206
+ return
207
+ state.last_hover_check = current_time
208
+
209
+ # Get mouse position relative to viewport
210
+ mouse_pos = dpg.get_mouse_pos(local=False)
211
+
212
+ # Get preview image position
213
+ if not dpg.does_item_exist("preview_image"):
214
+ return
215
+
216
+ img_pos = dpg.get_item_pos("preview_image")
217
+ panel_pos = dpg.get_item_pos("preview_panel")
218
+
219
+ # Calculate hover position relative to image
220
+ hover_x = mouse_pos[0] - panel_pos[0] - img_pos[0]
221
+ hover_y = mouse_pos[1] - panel_pos[1] - img_pos[1]
222
+
223
+ # Check if within image bounds
224
+ max_width, max_height = 800, 600
225
+ if not (0 <= hover_x <= max_width and 0 <= hover_y <= max_height):
226
+ if state.hovered_element is not None:
227
+ state.hovered_element = None
228
+ dpg.set_value("hover_text", "")
229
+ # Use fast overlay update instead of full redraw
230
+ update_hover_overlay(state, dpg)
231
+ return
232
+
233
+ # Find element under cursor
234
+ element = find_clicked_element(state, hover_x, hover_y)
235
+
236
+ if element is None:
237
+ # Check for trace hover
238
+ trace_idx = find_nearest_trace(state, hover_x, hover_y, max_width, max_height)
239
+ if trace_idx is not None:
240
+ element = {"type": "trace", "index": trace_idx}
241
+
242
+ # Check if hover changed
243
+ old_hover = state.hovered_element
244
+ if element != old_hover:
245
+ state.hovered_element = element
246
+ if element:
247
+ elem_type = element.get("type", "")
248
+ elem_idx = element.get("index")
249
+ if elem_type == "trace" and elem_idx is not None:
250
+ traces = state.current_overrides.get("traces", [])
251
+ if elem_idx < len(traces):
252
+ label = traces[elem_idx].get("label", f"Trace {elem_idx}")
253
+ dpg.set_value("hover_text", f"Hover: {label} (click to select)")
254
+ else:
255
+ label = elem_type.replace("x", "X ").replace("y", "Y ").title()
256
+ dpg.set_value("hover_text", f"Hover: {label} (click to select)")
257
+ else:
258
+ dpg.set_value("hover_text", "")
259
+
260
+ # Use fast overlay update for hover (no matplotlib re-render)
261
+ update_hover_overlay(state, dpg)
262
+
263
+
264
+ def on_element_selected(state: "EditorState", dpg, app_data) -> None:
265
+ """Handle element selection from combo box."""
266
+ from ._selection import select_element
267
+
268
+ if app_data == "Title":
269
+ select_element(state, {"type": "title", "index": None}, dpg)
270
+ elif app_data == "X Label":
271
+ select_element(state, {"type": "xlabel", "index": None}, dpg)
272
+ elif app_data == "Y Label":
273
+ select_element(state, {"type": "ylabel", "index": None}, dpg)
274
+ elif app_data == "X Axis":
275
+ select_element(state, {"type": "xaxis", "index": None}, dpg)
276
+ elif app_data == "Y Axis":
277
+ select_element(state, {"type": "yaxis", "index": None}, dpg)
278
+ elif app_data == "Legend":
279
+ select_element(state, {"type": "legend", "index": None}, dpg)
280
+ elif app_data.startswith("Trace: "):
281
+ # Find trace index
282
+ trace_label = app_data[7:] # Remove "Trace: " prefix
283
+ traces = state.current_overrides.get("traces", [])
284
+ for i, t in enumerate(traces):
285
+ if t.get("label", t.get("id", f"Trace {i}")) == trace_label:
286
+ select_element(state, {"type": "trace", "index": i}, dpg)
287
+ break
288
+
289
+
290
+ def on_text_element_change(state: "EditorState", dpg) -> None:
291
+ """Handle changes to text element properties."""
292
+ from ._rendering import update_preview
293
+
294
+ if state.selected_element is None:
295
+ return
296
+
297
+ elem_type = state.selected_element.get("type")
298
+ if elem_type not in ("title", "xlabel", "ylabel"):
299
+ return
300
+
301
+ text = dpg.get_value("element_text_input")
302
+ fontsize = dpg.get_value("element_fontsize_slider")
303
+
304
+ if elem_type == "title":
305
+ state.current_overrides["title"] = text
306
+ state.current_overrides["title_fontsize"] = fontsize
307
+ elif elem_type == "xlabel":
308
+ state.current_overrides["xlabel"] = text
309
+ state.current_overrides["axis_fontsize"] = fontsize
310
+ elif elem_type == "ylabel":
311
+ state.current_overrides["ylabel"] = text
312
+ state.current_overrides["axis_fontsize"] = fontsize
313
+
314
+ state.user_modified = True
315
+ update_preview(state, dpg)
316
+
317
+
318
+ def on_axis_element_change(state: "EditorState", dpg) -> None:
319
+ """Handle changes to axis element properties."""
320
+ from ._rendering import update_preview
321
+
322
+ if state.selected_element is None:
323
+ return
324
+
325
+ elem_type = state.selected_element.get("type")
326
+ if elem_type not in ("xaxis", "yaxis"):
327
+ return
328
+
329
+ state.current_overrides["axis_width"] = dpg.get_value("axis_linewidth_slider")
330
+ state.current_overrides["tick_length"] = dpg.get_value("axis_tick_length_slider")
331
+ state.current_overrides["tick_fontsize"] = dpg.get_value(
332
+ "axis_tick_fontsize_slider"
333
+ )
334
+
335
+ show_spine = dpg.get_value("axis_show_spine_checkbox")
336
+ if elem_type == "xaxis":
337
+ state.current_overrides["hide_bottom_spine"] = not show_spine
338
+ else:
339
+ state.current_overrides["hide_left_spine"] = not show_spine
340
+
341
+ state.user_modified = True
342
+ update_preview(state, dpg)
343
+
344
+
345
+ def on_legend_element_change(state: "EditorState", dpg) -> None:
346
+ """Handle changes to legend element properties."""
347
+ from ._rendering import update_preview
348
+
349
+ if state.selected_element is None:
350
+ return
351
+
352
+ elem_type = state.selected_element.get("type")
353
+ if elem_type != "legend":
354
+ return
355
+
356
+ state.current_overrides["legend_visible"] = dpg.get_value("legend_visible_edit")
357
+ state.current_overrides["legend_frameon"] = dpg.get_value("legend_frameon_edit")
358
+ state.current_overrides["legend_loc"] = dpg.get_value("legend_loc_edit")
359
+ state.current_overrides["legend_fontsize"] = dpg.get_value("legend_fontsize_edit")
360
+
361
+ state.user_modified = True
362
+ update_preview(state, dpg)
363
+
364
+
365
+ def on_trace_property_change(state: "EditorState", dpg) -> None:
366
+ """Handle changes to selected trace properties."""
367
+ from ._rendering import update_preview
368
+
369
+ if state.selected_trace_index is None:
370
+ return
371
+
372
+ traces = state.current_overrides.get("traces", [])
373
+ if state.selected_trace_index >= len(traces):
374
+ return
375
+
376
+ trace = traces[state.selected_trace_index]
377
+
378
+ # Update trace properties from widgets
379
+ trace["label"] = dpg.get_value("trace_label_input")
380
+
381
+ # Convert RGB to hex
382
+ color_rgb = dpg.get_value("trace_color_picker")
383
+ if color_rgb and len(color_rgb) >= 3:
384
+ r, g, b = int(color_rgb[0]), int(color_rgb[1]), int(color_rgb[2])
385
+ trace["color"] = f"#{r:02x}{g:02x}{b:02x}"
386
+
387
+ trace["linewidth"] = dpg.get_value("trace_linewidth_slider")
388
+ trace["linestyle"] = dpg.get_value("trace_linestyle_combo")
389
+
390
+ marker = dpg.get_value("trace_marker_combo")
391
+ trace["marker"] = marker if marker else None
392
+
393
+ trace["markersize"] = dpg.get_value("trace_markersize_slider")
394
+
395
+ state.user_modified = True
396
+ update_preview(state, dpg)
397
+
398
+
399
+ def on_save_manual(state: "EditorState", dpg) -> None:
400
+ """Save current overrides to .manual.json."""
401
+ from ..edit import save_manual_overrides
402
+
403
+ try:
404
+ collect_overrides(state, dpg)
405
+ manual_path = save_manual_overrides(state.json_path, state.current_overrides)
406
+ dpg.set_value("status_text", f"Saved: {manual_path.name}")
407
+ except Exception as e:
408
+ dpg.set_value("status_text", f"Error: {str(e)}")
409
+
410
+
411
+ def on_reset_overrides(state: "EditorState", dpg) -> None:
412
+ """Reset to initial overrides."""
413
+ from ._rendering import update_preview
414
+
415
+ state.current_overrides = copy.deepcopy(state.initial_overrides)
416
+ state.user_modified = False
417
+
418
+ # Update all widgets
419
+ dpg.set_value("title_input", state.current_overrides.get("title", ""))
420
+ dpg.set_value("xlabel_input", state.current_overrides.get("xlabel", ""))
421
+ dpg.set_value("ylabel_input", state.current_overrides.get("ylabel", ""))
422
+ dpg.set_value("linewidth_slider", state.current_overrides.get("linewidth", 1.0))
423
+ dpg.set_value("grid_checkbox", state.current_overrides.get("grid", False))
424
+ dpg.set_value(
425
+ "transparent_checkbox", state.current_overrides.get("transparent", True)
426
+ )
427
+
428
+ update_preview(state, dpg)
429
+ dpg.set_value("status_text", "Reset to original")
430
+
431
+
432
+ def on_export_png(state: "EditorState", dpg) -> None:
433
+ """Export current view to PNG."""
434
+ import matplotlib
435
+
436
+ matplotlib.use("Agg")
437
+ import matplotlib.pyplot as plt
438
+
439
+ from ._plotting import plot_from_csv
440
+
441
+ try:
442
+ collect_overrides(state, dpg)
443
+ output_path = state.json_path.with_suffix(".edited.png")
444
+
445
+ # Full resolution render
446
+ o = state.current_overrides
447
+ fig_size = o.get("fig_size", [3.15, 2.68])
448
+ dpi = o.get("dpi", 300)
449
+
450
+ fig, ax = plt.subplots(figsize=fig_size, dpi=dpi)
451
+
452
+ if state.csv_data is not None:
453
+ plot_from_csv(ax, o, state.csv_data)
454
+
455
+ if o.get("title"):
456
+ ax.set_title(o["title"], fontsize=o.get("title_fontsize", 8))
457
+ if o.get("xlabel"):
458
+ ax.set_xlabel(o["xlabel"], fontsize=o.get("axis_fontsize", 7))
459
+ if o.get("ylabel"):
460
+ ax.set_ylabel(o["ylabel"], fontsize=o.get("axis_fontsize", 7))
461
+
462
+ fig.tight_layout()
463
+ fig.savefig(
464
+ output_path,
465
+ dpi=dpi,
466
+ bbox_inches="tight",
467
+ transparent=o.get("transparent", True),
468
+ )
469
+ plt.close(fig)
470
+
471
+ dpg.set_value("status_text", f"Exported: {output_path.name}")
472
+ except Exception as e:
473
+ dpg.set_value("status_text", f"Error: {str(e)}")
474
+
475
+
476
+ # EOF
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/canvas/editor/_dearpygui/_panels/__init__.py
4
+
5
+ """
6
+ UI panel creation for DearPyGui editor.
7
+
8
+ Subpackage for organizing control panel sections.
9
+ """
10
+
11
+ from ._control import create_control_panel
12
+ from ._preview import create_preview_panel
13
+
14
+ __all__ = ["create_preview_panel", "create_control_panel"]
15
+
16
+
17
+ # EOF
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/canvas/editor/_dearpygui/_panels/_control.py
4
+
5
+ """
6
+ Control panel creation for DearPyGui editor.
7
+
8
+ Main orchestrator that delegates to section modules.
9
+ """
10
+
11
+ from typing import TYPE_CHECKING
12
+
13
+ if TYPE_CHECKING:
14
+ from .._state import EditorState
15
+
16
+
17
+ def create_control_panel(state: "EditorState", dpg) -> None:
18
+ """Create the control panel with all editing options."""
19
+ from .._handlers import (
20
+ on_add_annotation,
21
+ on_apply_limits,
22
+ on_axis_element_change,
23
+ on_element_selected,
24
+ on_export_png,
25
+ on_legend_element_change,
26
+ on_remove_annotation,
27
+ on_reset_overrides,
28
+ on_save_manual,
29
+ on_text_element_change,
30
+ on_trace_property_change,
31
+ on_value_change,
32
+ )
33
+ from .._rendering import update_preview
34
+ from .._selection import deselect_element, get_all_element_labels
35
+ from ._sections import (
36
+ create_annotations_section,
37
+ create_axis_limits_section,
38
+ create_dimensions_section,
39
+ create_font_settings_section,
40
+ create_labels_section,
41
+ create_legend_section,
42
+ create_line_style_section,
43
+ create_selected_element_section,
44
+ create_style_section,
45
+ create_tick_settings_section,
46
+ )
47
+
48
+ o = state.current_overrides
49
+
50
+ with dpg.child_window(width=-1, height=-1, tag="control_panel"):
51
+ dpg.add_text("Properties", color=(100, 200, 100))
52
+ dpg.add_separator()
53
+
54
+ # Labels Section
55
+ create_labels_section(state, dpg, o, on_value_change)
56
+
57
+ # Axis Limits Section
58
+ create_axis_limits_section(state, dpg, o, on_apply_limits)
59
+
60
+ # Line Style Section
61
+ create_line_style_section(state, dpg, o, on_value_change)
62
+
63
+ # Font Settings Section
64
+ create_font_settings_section(state, dpg, o, on_value_change)
65
+
66
+ # Tick Settings Section
67
+ create_tick_settings_section(state, dpg, o, on_value_change)
68
+
69
+ # Style Section
70
+ create_style_section(state, dpg, o, on_value_change)
71
+
72
+ # Legend Section
73
+ create_legend_section(state, dpg, o, on_value_change)
74
+
75
+ # Selected Element Section
76
+ create_selected_element_section(
77
+ state,
78
+ dpg,
79
+ get_all_element_labels,
80
+ on_element_selected,
81
+ on_trace_property_change,
82
+ on_text_element_change,
83
+ on_axis_element_change,
84
+ on_legend_element_change,
85
+ deselect_element,
86
+ )
87
+
88
+ # Dimensions Section
89
+ create_dimensions_section(state, dpg, o, on_value_change)
90
+
91
+ # Annotations Section
92
+ create_annotations_section(state, dpg, on_add_annotation, on_remove_annotation)
93
+
94
+ dpg.add_separator()
95
+
96
+ # Action buttons
97
+ dpg.add_button(
98
+ label="Update Preview",
99
+ callback=lambda: update_preview(state, dpg),
100
+ width=-1,
101
+ )
102
+ dpg.add_button(
103
+ label="Save to .manual.json",
104
+ callback=lambda: on_save_manual(state, dpg),
105
+ width=-1,
106
+ )
107
+ dpg.add_button(
108
+ label="Reset to Original",
109
+ callback=lambda: on_reset_overrides(state, dpg),
110
+ width=-1,
111
+ )
112
+ dpg.add_button(
113
+ label="Export PNG",
114
+ callback=lambda: on_export_png(state, dpg),
115
+ width=-1,
116
+ )
117
+
118
+
119
+ # EOF