scitex 2.14.0__py3-none-any.whl → 2.15.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 (218) hide show
  1. scitex/__init__.py +47 -0
  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 +191 -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/writer.py +21 -204
  17. scitex/ai/_gen_ai/_PARAMS.py +10 -7
  18. scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
  19. scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
  20. scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
  21. scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
  22. scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
  23. scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
  24. scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
  25. scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
  26. scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
  27. scitex/audio/README.md +40 -36
  28. scitex/audio/__init__.py +127 -59
  29. scitex/audio/_branding.py +185 -0
  30. scitex/audio/_mcp/__init__.py +32 -0
  31. scitex/audio/_mcp/handlers.py +59 -6
  32. scitex/audio/_mcp/speak_handlers.py +238 -0
  33. scitex/audio/_relay.py +225 -0
  34. scitex/audio/engines/elevenlabs_engine.py +6 -1
  35. scitex/audio/mcp_server.py +228 -75
  36. scitex/canvas/README.md +1 -1
  37. scitex/canvas/editor/_dearpygui/__init__.py +25 -0
  38. scitex/canvas/editor/_dearpygui/_editor.py +147 -0
  39. scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
  40. scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
  41. scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
  42. scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
  43. scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
  44. scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
  45. scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
  46. scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
  47. scitex/canvas/editor/_dearpygui/_selection.py +295 -0
  48. scitex/canvas/editor/_dearpygui/_state.py +93 -0
  49. scitex/canvas/editor/_dearpygui/_utils.py +61 -0
  50. scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
  51. scitex/cli/__init__.py +38 -43
  52. scitex/cli/audio.py +76 -27
  53. scitex/cli/capture.py +13 -20
  54. scitex/cli/introspect.py +443 -0
  55. scitex/cli/main.py +198 -109
  56. scitex/cli/mcp.py +60 -34
  57. scitex/cli/scholar/__init__.py +8 -0
  58. scitex/cli/scholar/_crossref_scitex.py +296 -0
  59. scitex/cli/scholar/_fetch.py +25 -3
  60. scitex/cli/social.py +314 -0
  61. scitex/cli/writer.py +117 -0
  62. scitex/config/README.md +1 -1
  63. scitex/config/__init__.py +16 -2
  64. scitex/config/_env_registry.py +191 -0
  65. scitex/diagram/__init__.py +42 -19
  66. scitex/diagram/mcp_server.py +13 -125
  67. scitex/introspect/__init__.py +75 -0
  68. scitex/introspect/_call_graph.py +303 -0
  69. scitex/introspect/_class_hierarchy.py +163 -0
  70. scitex/introspect/_core.py +42 -0
  71. scitex/introspect/_docstring.py +131 -0
  72. scitex/introspect/_examples.py +113 -0
  73. scitex/introspect/_imports.py +271 -0
  74. scitex/introspect/_mcp/__init__.py +37 -0
  75. scitex/introspect/_mcp/handlers.py +208 -0
  76. scitex/introspect/_members.py +151 -0
  77. scitex/introspect/_resolve.py +89 -0
  78. scitex/introspect/_signature.py +131 -0
  79. scitex/introspect/_source.py +80 -0
  80. scitex/introspect/_type_hints.py +172 -0
  81. scitex/io/bundle/README.md +1 -1
  82. scitex/mcp_server.py +98 -5
  83. scitex/plt/__init__.py +248 -550
  84. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
  85. scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  86. scitex/plt/gallery/README.md +1 -1
  87. scitex/plt/utils/_hitmap/__init__.py +82 -0
  88. scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
  89. scitex/plt/utils/_hitmap/_color_application.py +346 -0
  90. scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
  91. scitex/plt/utils/_hitmap/_constants.py +40 -0
  92. scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
  93. scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
  94. scitex/plt/utils/_hitmap/_query.py +113 -0
  95. scitex/plt/utils/_hitmap.py +46 -1616
  96. scitex/plt/utils/_metadata/__init__.py +80 -0
  97. scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
  98. scitex/plt/utils/_metadata/_artists/_base.py +195 -0
  99. scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
  100. scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
  101. scitex/plt/utils/_metadata/_artists/_images.py +80 -0
  102. scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
  103. scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
  104. scitex/plt/utils/_metadata/_artists/_text.py +106 -0
  105. scitex/plt/utils/_metadata/_csv.py +416 -0
  106. scitex/plt/utils/_metadata/_detect.py +225 -0
  107. scitex/plt/utils/_metadata/_legend.py +127 -0
  108. scitex/plt/utils/_metadata/_rounding.py +117 -0
  109. scitex/plt/utils/_metadata/_verification.py +202 -0
  110. scitex/schema/README.md +1 -1
  111. scitex/scholar/__init__.py +8 -0
  112. scitex/scholar/_mcp/crossref_handlers.py +265 -0
  113. scitex/scholar/core/Scholar.py +63 -1700
  114. scitex/scholar/core/_mixins/__init__.py +36 -0
  115. scitex/scholar/core/_mixins/_enrichers.py +270 -0
  116. scitex/scholar/core/_mixins/_library_handlers.py +100 -0
  117. scitex/scholar/core/_mixins/_loaders.py +103 -0
  118. scitex/scholar/core/_mixins/_pdf_download.py +375 -0
  119. scitex/scholar/core/_mixins/_pipeline.py +312 -0
  120. scitex/scholar/core/_mixins/_project_handlers.py +125 -0
  121. scitex/scholar/core/_mixins/_savers.py +69 -0
  122. scitex/scholar/core/_mixins/_search.py +103 -0
  123. scitex/scholar/core/_mixins/_services.py +88 -0
  124. scitex/scholar/core/_mixins/_url_finding.py +105 -0
  125. scitex/scholar/crossref_scitex.py +367 -0
  126. scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  127. scitex/scholar/examples/00_run_all.sh +120 -0
  128. scitex/scholar/jobs/_executors.py +27 -3
  129. scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
  130. scitex/scholar/pdf_download/_cli.py +154 -0
  131. scitex/scholar/pdf_download/strategies/__init__.py +11 -8
  132. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
  133. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
  134. scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
  135. scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
  136. scitex/scholar/pipelines/_single_steps.py +71 -36
  137. scitex/scholar/storage/_LibraryManager.py +97 -1695
  138. scitex/scholar/storage/_mixins/__init__.py +30 -0
  139. scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
  140. scitex/scholar/storage/_mixins/_library_operations.py +218 -0
  141. scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
  142. scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
  143. scitex/scholar/storage/_mixins/_resolution.py +376 -0
  144. scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
  145. scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
  146. scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
  147. scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
  148. scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
  149. scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
  150. scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
  151. scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
  152. scitex/security/README.md +3 -3
  153. scitex/session/README.md +1 -1
  154. scitex/sh/README.md +1 -1
  155. scitex/social/__init__.py +153 -0
  156. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  157. scitex/template/README.md +1 -1
  158. scitex/template/clone_writer_directory.py +5 -5
  159. scitex/writer/README.md +1 -1
  160. scitex/writer/_mcp/handlers.py +11 -744
  161. scitex/writer/_mcp/tool_schemas.py +5 -335
  162. scitex-2.15.1.dist-info/METADATA +648 -0
  163. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/RECORD +166 -111
  164. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  165. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  166. scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
  167. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
  168. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
  169. scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
  170. scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
  171. scitex/diagram/_compile.py +0 -312
  172. scitex/diagram/_diagram.py +0 -355
  173. scitex/diagram/_mcp/__init__.py +0 -4
  174. scitex/diagram/_mcp/handlers.py +0 -400
  175. scitex/diagram/_mcp/tool_schemas.py +0 -157
  176. scitex/diagram/_presets.py +0 -173
  177. scitex/diagram/_schema.py +0 -182
  178. scitex/diagram/_split.py +0 -278
  179. scitex/plt/_mcp/__init__.py +0 -4
  180. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  181. scitex/plt/_mcp/_handlers_figure.py +0 -195
  182. scitex/plt/_mcp/_handlers_plot.py +0 -252
  183. scitex/plt/_mcp/_handlers_style.py +0 -219
  184. scitex/plt/_mcp/handlers.py +0 -74
  185. scitex/plt/_mcp/tool_schemas.py +0 -497
  186. scitex/plt/mcp_server.py +0 -231
  187. scitex/scholar/data/.gitkeep +0 -0
  188. scitex/scholar/data/README.md +0 -44
  189. scitex/scholar/data/bib_files/bibliography.bib +0 -1952
  190. scitex/scholar/data/bib_files/neurovista.bib +0 -277
  191. scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
  192. scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
  193. scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
  194. scitex/scholar/data/bib_files/openaccess.bib +0 -89
  195. scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
  196. scitex/scholar/data/bib_files/pac.bib +0 -698
  197. scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
  198. scitex/scholar/data/bib_files/pac_processed.bib +0 -0
  199. scitex/scholar/data/bib_files/pac_titles.txt +0 -75
  200. scitex/scholar/data/bib_files/paywalled.bib +0 -98
  201. scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
  202. scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
  203. scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
  204. scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
  205. scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
  206. scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
  207. scitex/scholar/data/bib_files/test_seizure.bib +0 -46
  208. scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
  209. scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
  210. scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
  211. scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
  212. scitex/scholar/data/impact_factor.db +0 -0
  213. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  214. scitex/scholar/examples/dev.py +0 -38
  215. scitex-2.14.0.dist-info/METADATA +0 -1238
  216. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/WHEEL +0 -0
  217. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/entry_points.txt +0 -0
  218. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,295 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/canvas/editor/_dearpygui/_selection.py
4
+
5
+ """
6
+ Element selection for DearPyGui editor.
7
+
8
+ Handles click-to-select, hover detection, and element finding.
9
+ """
10
+
11
+ from typing import TYPE_CHECKING, Dict, List, Optional
12
+
13
+ import numpy as np
14
+ import pandas as pd
15
+
16
+ if TYPE_CHECKING:
17
+ from ._state import EditorState
18
+
19
+
20
+ def get_trace_labels(state: "EditorState") -> List[str]:
21
+ """Get list of trace labels for selection combo."""
22
+ traces = state.current_overrides.get("traces", [])
23
+ if not traces:
24
+ return ["(no traces)"]
25
+ return [t.get("label", t.get("id", f"Trace {i}")) for i, t in enumerate(traces)]
26
+
27
+
28
+ def get_all_element_labels(state: "EditorState") -> List[str]:
29
+ """Get list of all selectable element labels."""
30
+ labels = []
31
+
32
+ # Fixed elements
33
+ labels.append("Title")
34
+ labels.append("X Label")
35
+ labels.append("Y Label")
36
+ labels.append("X Axis")
37
+ labels.append("Y Axis")
38
+ labels.append("Legend")
39
+
40
+ # Traces
41
+ traces = state.current_overrides.get("traces", [])
42
+ for i, t in enumerate(traces):
43
+ label = t.get("label", t.get("id", f"Trace {i}"))
44
+ labels.append(f"Trace: {label}")
45
+
46
+ return labels
47
+
48
+
49
+ def find_clicked_element(
50
+ state: "EditorState", click_x: float, click_y: float
51
+ ) -> Optional[Dict]:
52
+ """Find which element was clicked based on stored bboxes."""
53
+ if not state.element_bboxes:
54
+ return None
55
+
56
+ # Check each element bbox
57
+ for element_type, bbox in state.element_bboxes.items():
58
+ if bbox is None:
59
+ continue
60
+ x0, y0, x1, y1 = bbox
61
+ if x0 <= click_x <= x1 and y0 <= click_y <= y1:
62
+ return {"type": element_type, "index": None}
63
+
64
+ return None
65
+
66
+
67
+ def find_nearest_trace(
68
+ state: "EditorState",
69
+ click_x: float,
70
+ click_y: float,
71
+ preview_width: int,
72
+ preview_height: int,
73
+ ) -> Optional[int]:
74
+ """Find the nearest trace to the click position."""
75
+ if state.csv_data is None or not isinstance(state.csv_data, pd.DataFrame):
76
+ return None
77
+
78
+ traces = state.current_overrides.get("traces", [])
79
+ if not traces:
80
+ return None
81
+
82
+ # Get preview bounds from last render
83
+ if state.preview_bounds is None:
84
+ return None
85
+
86
+ x_offset, y_offset, fig_width, fig_height = state.preview_bounds
87
+
88
+ # Adjust click coordinates to figure space
89
+ fig_x = click_x - x_offset
90
+ fig_y = click_y - y_offset
91
+
92
+ # Check if click is within figure bounds
93
+ if not (0 <= fig_x <= fig_width and 0 <= fig_y <= fig_height):
94
+ return None
95
+
96
+ # Get axes transform info
97
+ if state.axes_transform is None:
98
+ return None
99
+
100
+ ax_x0, ax_y0, ax_width, ax_height, xlim, ylim = state.axes_transform
101
+
102
+ # Convert figure pixel to axes pixel
103
+ ax_pixel_x = fig_x - ax_x0
104
+ ax_pixel_y = fig_y - ax_y0
105
+
106
+ # Check if click is within axes bounds
107
+ if not (0 <= ax_pixel_x <= ax_width and 0 <= ax_pixel_y <= ax_height):
108
+ return None
109
+
110
+ # Convert axes pixel to data coordinates
111
+ # Note: y is flipped (0 at top in pixel space)
112
+ data_x = xlim[0] + (ax_pixel_x / ax_width) * (xlim[1] - xlim[0])
113
+ data_y = ylim[1] - (ax_pixel_y / ax_height) * (ylim[1] - ylim[0])
114
+
115
+ # Find nearest trace
116
+ df = state.csv_data
117
+ min_dist = float("inf")
118
+ nearest_idx = None
119
+
120
+ for i, trace in enumerate(traces):
121
+ csv_cols = trace.get("csv_columns", {})
122
+ x_col = csv_cols.get("x")
123
+ y_col = csv_cols.get("y")
124
+
125
+ if x_col not in df.columns or y_col not in df.columns:
126
+ continue
127
+
128
+ trace_x = df[x_col].dropna().values
129
+ trace_y = df[y_col].dropna().values
130
+
131
+ if len(trace_x) == 0:
132
+ continue
133
+
134
+ # Normalize coordinates for distance calculation
135
+ x_range = xlim[1] - xlim[0]
136
+ y_range = ylim[1] - ylim[0]
137
+
138
+ norm_click_x = (data_x - xlim[0]) / x_range if x_range > 0 else 0
139
+ norm_click_y = (data_y - ylim[0]) / y_range if y_range > 0 else 0
140
+
141
+ norm_trace_x = (trace_x - xlim[0]) / x_range if x_range > 0 else trace_x
142
+ norm_trace_y = (trace_y - ylim[0]) / y_range if y_range > 0 else trace_y
143
+
144
+ # Calculate distances to all points
145
+ distances = np.sqrt(
146
+ (norm_trace_x - norm_click_x) ** 2 + (norm_trace_y - norm_click_y) ** 2
147
+ )
148
+ min_trace_dist = np.min(distances)
149
+
150
+ if min_trace_dist < min_dist:
151
+ min_dist = min_trace_dist
152
+ nearest_idx = i
153
+
154
+ # Only select if close enough (threshold in normalized space)
155
+ if min_dist < 0.1: # 10% of plot area
156
+ return nearest_idx
157
+
158
+ return None
159
+
160
+
161
+ def select_element(state: "EditorState", element: Dict, dpg) -> None:
162
+ """Select an element and show appropriate controls."""
163
+ from ._rendering import update_preview
164
+
165
+ state.selected_element = element
166
+ elem_type = element.get("type")
167
+ elem_idx = element.get("index")
168
+
169
+ # Hide all control groups first
170
+ dpg.configure_item("trace_controls_group", show=False)
171
+ dpg.configure_item("text_controls_group", show=False)
172
+ dpg.configure_item("axis_controls_group", show=False)
173
+ dpg.configure_item("legend_controls_group", show=False)
174
+
175
+ # Update combo selection
176
+ if elem_type == "trace":
177
+ _select_trace(state, elem_idx, dpg)
178
+ elif elem_type in ("title", "xlabel", "ylabel"):
179
+ _select_text_element(state, elem_type, dpg)
180
+ elif elem_type in ("xaxis", "yaxis"):
181
+ _select_axis_element(state, elem_type, dpg)
182
+ elif elem_type == "legend":
183
+ _select_legend(state, dpg)
184
+
185
+ # Redraw with highlight
186
+ update_preview(state, dpg)
187
+
188
+
189
+ def _select_trace(state: "EditorState", trace_idx: Optional[int], dpg) -> None:
190
+ """Handle trace selection."""
191
+ traces = state.current_overrides.get("traces", [])
192
+ if trace_idx is not None and trace_idx < len(traces):
193
+ trace = traces[trace_idx]
194
+ label = f"Trace: {trace.get('label', trace.get('id', f'Trace {trace_idx}'))}"
195
+ dpg.set_value("element_selector_combo", label)
196
+
197
+ # Show trace controls and populate
198
+ dpg.configure_item("trace_controls_group", show=True)
199
+ state.selected_trace_index = trace_idx
200
+ dpg.set_value("trace_label_input", trace.get("label", ""))
201
+
202
+ color_hex = trace.get("color", "#0080bf")
203
+ try:
204
+ r = int(color_hex[1:3], 16)
205
+ g = int(color_hex[3:5], 16)
206
+ b = int(color_hex[5:7], 16)
207
+ dpg.set_value("trace_color_picker", [r, g, b])
208
+ except (ValueError, IndexError):
209
+ dpg.set_value("trace_color_picker", [128, 128, 191])
210
+
211
+ dpg.set_value("trace_linewidth_slider", trace.get("linewidth", 1.0))
212
+ dpg.set_value("trace_linestyle_combo", trace.get("linestyle", "-"))
213
+ dpg.set_value("trace_marker_combo", trace.get("marker", "") or "")
214
+ dpg.set_value("trace_markersize_slider", trace.get("markersize", 6.0))
215
+
216
+ dpg.set_value(
217
+ "selection_text",
218
+ f"Selected: {trace.get('label', f'Trace {trace_idx}')}",
219
+ )
220
+
221
+
222
+ def _select_text_element(state: "EditorState", elem_type: str, dpg) -> None:
223
+ """Handle text element selection (title, xlabel, ylabel)."""
224
+ dpg.set_value(
225
+ "element_selector_combo",
226
+ elem_type.replace("x", "X ").replace("y", "Y ").title(),
227
+ )
228
+ dpg.configure_item("text_controls_group", show=True)
229
+
230
+ o = state.current_overrides
231
+ if elem_type == "title":
232
+ dpg.set_value("element_text_input", o.get("title", ""))
233
+ dpg.set_value("element_fontsize_slider", o.get("title_fontsize", 8))
234
+ elif elem_type == "xlabel":
235
+ dpg.set_value("element_text_input", o.get("xlabel", ""))
236
+ dpg.set_value("element_fontsize_slider", o.get("axis_fontsize", 7))
237
+ elif elem_type == "ylabel":
238
+ dpg.set_value("element_text_input", o.get("ylabel", ""))
239
+ dpg.set_value("element_fontsize_slider", o.get("axis_fontsize", 7))
240
+
241
+ dpg.set_value("selection_text", f"Selected: {elem_type.title()}")
242
+
243
+
244
+ def _select_axis_element(state: "EditorState", elem_type: str, dpg) -> None:
245
+ """Handle axis element selection (xaxis, yaxis)."""
246
+ label = "X Axis" if elem_type == "xaxis" else "Y Axis"
247
+ dpg.set_value("element_selector_combo", label)
248
+ dpg.configure_item("axis_controls_group", show=True)
249
+
250
+ o = state.current_overrides
251
+ dpg.set_value("axis_linewidth_slider", o.get("axis_width", 0.2))
252
+ dpg.set_value("axis_tick_length_slider", o.get("tick_length", 0.8))
253
+ dpg.set_value("axis_tick_fontsize_slider", o.get("tick_fontsize", 7))
254
+
255
+ if elem_type == "xaxis":
256
+ dpg.set_value("axis_show_spine_checkbox", not o.get("hide_bottom_spine", False))
257
+ else:
258
+ dpg.set_value("axis_show_spine_checkbox", not o.get("hide_left_spine", False))
259
+
260
+ dpg.set_value("selection_text", f"Selected: {label}")
261
+
262
+
263
+ def _select_legend(state: "EditorState", dpg) -> None:
264
+ """Handle legend selection."""
265
+ dpg.set_value("element_selector_combo", "Legend")
266
+ dpg.configure_item("legend_controls_group", show=True)
267
+
268
+ o = state.current_overrides
269
+ dpg.set_value("legend_visible_edit", o.get("legend_visible", True))
270
+ dpg.set_value("legend_frameon_edit", o.get("legend_frameon", False))
271
+ dpg.set_value("legend_loc_edit", o.get("legend_loc", "best"))
272
+ dpg.set_value("legend_fontsize_edit", o.get("legend_fontsize", 6))
273
+
274
+ dpg.set_value("selection_text", "Selected: Legend")
275
+
276
+
277
+ def deselect_element(state: "EditorState", dpg) -> None:
278
+ """Deselect the current element."""
279
+ from ._rendering import update_preview
280
+
281
+ state.selected_element = None
282
+ state.selected_trace_index = None
283
+
284
+ # Hide all control groups
285
+ dpg.configure_item("trace_controls_group", show=False)
286
+ dpg.configure_item("text_controls_group", show=False)
287
+ dpg.configure_item("axis_controls_group", show=False)
288
+ dpg.configure_item("legend_controls_group", show=False)
289
+
290
+ dpg.set_value("selection_text", "")
291
+ dpg.set_value("element_selector_combo", "")
292
+ update_preview(state, dpg)
293
+
294
+
295
+ # EOF
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/canvas/editor/_dearpygui/_state.py
4
+
5
+ """
6
+ Editor state management for DearPyGui editor.
7
+
8
+ Provides EditorState dataclass to hold all editor state.
9
+ """
10
+
11
+ import copy
12
+ from dataclasses import dataclass, field
13
+ from pathlib import Path
14
+ from typing import Any, Dict, List, Optional, Tuple
15
+
16
+
17
+ @dataclass
18
+ class EditorState:
19
+ """Holds all state for the DearPyGui editor."""
20
+
21
+ # Core data
22
+ json_path: Path
23
+ metadata: Dict[str, Any]
24
+ csv_data: Optional[Any] = None
25
+ png_path: Optional[Path] = None
26
+ manual_overrides: Dict[str, Any] = field(default_factory=dict)
27
+
28
+ # Defaults
29
+ scitex_defaults: Dict[str, Any] = field(default_factory=dict)
30
+ metadata_defaults: Dict[str, Any] = field(default_factory=dict)
31
+ current_overrides: Dict[str, Any] = field(default_factory=dict)
32
+
33
+ # Modification tracking
34
+ initial_overrides: Dict[str, Any] = field(default_factory=dict)
35
+ user_modified: bool = False
36
+ texture_id: Optional[int] = None
37
+
38
+ # Selection state
39
+ selected_element: Optional[Dict[str, Any]] = None
40
+ selected_trace_index: Optional[int] = None
41
+
42
+ # Preview bounds
43
+ preview_bounds: Optional[Tuple[int, int, int, int]] = None
44
+ axes_transform: Optional[Tuple] = None
45
+ element_bboxes: Dict[str, Tuple[int, int, int, int]] = field(default_factory=dict)
46
+ element_bboxes_raw: Dict[str, Tuple] = field(default_factory=dict)
47
+
48
+ # Hover state
49
+ hovered_element: Optional[Dict[str, Any]] = None
50
+ last_hover_check: float = 0
51
+ backend_name: str = "dearpygui"
52
+
53
+ # Cached rendering
54
+ cached_base_image: Optional[Any] = None
55
+ cached_base_data: Optional[List[float]] = None
56
+ cache_dirty: bool = True
57
+
58
+ @classmethod
59
+ def create(
60
+ cls,
61
+ json_path: Path,
62
+ metadata: Dict[str, Any],
63
+ csv_data: Optional[Any] = None,
64
+ png_path: Optional[Path] = None,
65
+ manual_overrides: Optional[Dict[str, Any]] = None,
66
+ ) -> "EditorState":
67
+ """Create an EditorState with properly initialized defaults."""
68
+ from .._defaults import extract_defaults_from_metadata, get_scitex_defaults
69
+
70
+ state = cls(
71
+ json_path=Path(json_path),
72
+ metadata=metadata,
73
+ csv_data=csv_data,
74
+ png_path=Path(png_path) if png_path else None,
75
+ manual_overrides=manual_overrides or {},
76
+ )
77
+
78
+ # Get SciTeX defaults and merge with metadata
79
+ state.scitex_defaults = get_scitex_defaults()
80
+ state.metadata_defaults = extract_defaults_from_metadata(metadata)
81
+
82
+ # Start with defaults, then overlay manual overrides
83
+ state.current_overrides = copy.deepcopy(state.scitex_defaults)
84
+ state.current_overrides.update(state.metadata_defaults)
85
+ state.current_overrides.update(state.manual_overrides)
86
+
87
+ # Track modifications
88
+ state.initial_overrides = copy.deepcopy(state.current_overrides)
89
+
90
+ return state
91
+
92
+
93
+ # EOF
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/canvas/editor/_dearpygui/_utils.py
4
+
5
+ """
6
+ Utility functions for DearPyGui editor.
7
+
8
+ Provides helper functions like checkerboard pattern creation for transparency preview.
9
+ """
10
+
11
+ from typing import TYPE_CHECKING
12
+
13
+ if TYPE_CHECKING:
14
+ from PIL import Image
15
+
16
+
17
+ def create_checkerboard(width: int, height: int, square_size: int = 10) -> "Image":
18
+ """Create a checkerboard pattern image for transparency preview.
19
+
20
+ Parameters
21
+ ----------
22
+ width : int
23
+ Image width in pixels
24
+ height : int
25
+ Image height in pixels
26
+ square_size : int
27
+ Size of each checkerboard square (default: 10)
28
+
29
+ Returns
30
+ -------
31
+ PIL.Image
32
+ RGBA image with checkerboard pattern (light/dark gray)
33
+ """
34
+ import numpy as np
35
+ from PIL import Image
36
+
37
+ # Create checkerboard pattern
38
+ light_gray = (220, 220, 220, 255)
39
+ dark_gray = (180, 180, 180, 255)
40
+
41
+ # Create array
42
+ img_array = np.zeros((height, width, 4), dtype=np.uint8)
43
+
44
+ for y in range(height):
45
+ for x in range(width):
46
+ # Determine which square we're in
47
+ square_x = x // square_size
48
+ square_y = y // square_size
49
+ if (square_x + square_y) % 2 == 0:
50
+ img_array[y, x] = light_gray
51
+ else:
52
+ img_array[y, x] = dark_gray
53
+
54
+ return Image.fromarray(img_array, "RGBA")
55
+
56
+
57
+ # mm to pt conversion factor
58
+ MM_TO_PT = 2.83465
59
+
60
+
61
+ # EOF
@@ -1,35 +1,20 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  # File: ./src/scitex/vis/editor/flask_editor/templates/__init__.py
4
3
  """Template components for Flask editor.
5
4
 
6
- Supports two modes:
7
- - Static files mode (default): Uses external CSS/JS files from static/
8
- - Inline mode (fallback): Embeds CSS/JS directly in HTML
5
+ Uses external CSS/JS files from static/ directory.
9
6
  """
10
7
 
11
8
  from ._html import HTML_BODY
12
9
 
13
- # Configuration flag - set to False to use inline mode for debugging
14
- USE_STATIC_FILES = True
15
10
 
16
-
17
- def build_html_template(use_static: bool = None) -> str:
18
- """Build the complete HTML template from components.
19
-
20
- Args:
21
- use_static: Override static file usage. If None, uses USE_STATIC_FILES.
11
+ def build_html_template() -> str:
12
+ """Build the complete HTML template.
22
13
 
23
14
  Returns:
24
15
  Complete HTML template string.
25
16
  """
26
- if use_static is None:
27
- use_static = USE_STATIC_FILES
28
-
29
- if use_static:
30
- return _build_static_template()
31
- else:
32
- return _build_inline_template()
17
+ return _build_static_template()
33
18
 
34
19
 
35
20
  def _build_static_template() -> str:
@@ -37,42 +22,44 @@ def _build_static_template() -> str:
37
22
  # Get list of JS files in correct load order
38
23
  js_files = [
39
24
  # Dev tools (load first to capture console logs)
40
- 'js/dev/element-inspector.js',
25
+ "js/dev/element-inspector.js",
41
26
  # Core modules first (dependencies)
42
- 'js/core/state.js',
43
- 'js/core/utils.js',
44
- 'js/core/api.js',
27
+ "js/core/state.js",
28
+ "js/core/utils.js",
29
+ "js/core/api.js",
45
30
  # Editor modules
46
- 'js/editor/bbox.js',
47
- 'js/editor/overlay.js',
48
- 'js/editor/preview.js',
49
- 'js/editor/element-drag.js',
31
+ "js/editor/bbox.js",
32
+ "js/editor/overlay.js",
33
+ "js/editor/preview.js",
34
+ "js/editor/element-drag.js",
50
35
  # Canvas modules
51
- 'js/canvas/selection.js',
52
- 'js/canvas/resize.js',
53
- 'js/canvas/dragging.js',
54
- 'js/canvas/canvas.js',
36
+ "js/canvas/selection.js",
37
+ "js/canvas/resize.js",
38
+ "js/canvas/dragging.js",
39
+ "js/canvas/canvas.js",
55
40
  # Alignment modules
56
- 'js/alignment/basic.js',
57
- 'js/alignment/axis.js',
58
- 'js/alignment/distribute.js',
41
+ "js/alignment/basic.js",
42
+ "js/alignment/axis.js",
43
+ "js/alignment/distribute.js",
59
44
  # UI modules
60
- 'js/ui/theme.js',
61
- 'js/ui/help.js',
62
- 'js/ui/download.js',
63
- 'js/ui/controls.js',
45
+ "js/ui/theme.js",
46
+ "js/ui/help.js",
47
+ "js/ui/download.js",
48
+ "js/ui/controls.js",
64
49
  # Shortcuts
65
- 'js/shortcuts/context-menu.js',
66
- 'js/shortcuts/keyboard.js',
50
+ "js/shortcuts/context-menu.js",
51
+ "js/shortcuts/keyboard.js",
67
52
  # Main entry (last)
68
- 'js/main.js',
53
+ "js/main.js",
69
54
  ]
70
55
 
71
56
  # Generate script tags
72
- script_tags = '\n '.join([
73
- f'<script src="{{{{ url_for(\'static\', filename=\'{f}\') }}}}"></script>'
74
- for f in js_files
75
- ])
57
+ script_tags = "\n ".join(
58
+ [
59
+ f"<script src=\"{{{{ url_for('static', filename='{f}') }}}}\"></script>"
60
+ for f in js_files
61
+ ]
62
+ )
76
63
 
77
64
  return f"""<!DOCTYPE html>
78
65
  <html lang="en" data-theme="dark">
@@ -95,29 +82,4 @@ def _build_static_template() -> str:
95
82
  """
96
83
 
97
84
 
98
- def _build_inline_template() -> str:
99
- """Build template with inline CSS/JS (fallback mode)."""
100
- from ._styles import CSS_STYLES
101
- from ._scripts import JS_SCRIPTS
102
-
103
- return f"""<!DOCTYPE html>
104
- <html lang="en" data-theme="dark">
105
- <head>
106
- <meta charset="UTF-8">
107
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
108
- <title>SciTeX Figure Editor - {{{{ filename }}}}</title>
109
- <style>
110
- {CSS_STYLES}
111
- </style>
112
- </head>
113
- <body>
114
- {HTML_BODY}
115
- <script>
116
- {JS_SCRIPTS}
117
- </script>
118
- </body>
119
- </html>
120
- """
121
-
122
-
123
85
  # EOF
scitex/cli/__init__.py CHANGED
@@ -16,49 +16,44 @@ import click
16
16
  from .main import cli
17
17
 
18
18
 
19
- def add_help_recursive(group: click.Group, parent_name: str = "scitex") -> None:
20
- """Add help-recursive command to a click group.
19
+ def print_help_recursive(ctx, group: click.Group) -> None:
20
+ """Print help for a group and all its subcommands.
21
21
 
22
22
  Args:
23
- group: The click group to add the command to
24
- parent_name: The parent command name for display (e.g., "scitex writer")
23
+ ctx: The click context
24
+ group: The click group to print help for
25
25
  """
26
-
27
- @group.command("help-recursive")
28
- @click.pass_context
29
- def help_recursive(ctx):
30
- """Show help for all commands recursively."""
31
- # Create a fake parent context to show correct command path in Usage
32
- fake_parent = click.Context(click.Group(), info_name="scitex")
33
- parent_ctx = click.Context(group, info_name=group.name, parent=fake_parent)
34
-
35
- click.secho(f"━━━ scitex {group.name} ━━━", fg="cyan", bold=True)
36
- click.echo(group.get_help(parent_ctx))
37
-
38
- for name in sorted(group.list_commands(ctx) or []):
39
- cmd = group.get_command(ctx, name)
40
- if cmd is None or name == "help-recursive":
41
- continue
42
- click.echo()
43
- click.secho(f"━━━ scitex {group.name} {name} ━━━", fg="cyan", bold=True)
44
- with click.Context(cmd, info_name=name, parent=parent_ctx) as sub_ctx:
45
- click.echo(cmd.get_help(sub_ctx))
46
- # Handle nested subgroups
47
- if isinstance(cmd, click.Group):
48
- for sub_name in sorted(cmd.list_commands(sub_ctx) or []):
49
- sub_cmd = cmd.get_command(sub_ctx, sub_name)
50
- if sub_cmd is None:
51
- continue
52
- click.echo()
53
- click.secho(
54
- f"━━━ scitex {group.name} {name} {sub_name} ━━━",
55
- fg="cyan",
56
- bold=True,
57
- )
58
- with click.Context(
59
- sub_cmd, info_name=sub_name, parent=sub_ctx
60
- ) as sub_sub_ctx:
61
- click.echo(sub_cmd.get_help(sub_sub_ctx))
62
-
63
-
64
- __all__ = ["cli", "add_help_recursive"]
26
+ # Create a fake parent context to show correct command path in Usage
27
+ fake_parent = click.Context(click.Group(), info_name="scitex")
28
+ parent_ctx = click.Context(group, info_name=group.name, parent=fake_parent)
29
+
30
+ click.secho(f"━━━ scitex {group.name} ━━━", fg="cyan", bold=True)
31
+ click.echo(group.get_help(parent_ctx))
32
+
33
+ for name in sorted(group.list_commands(ctx) or []):
34
+ cmd = group.get_command(ctx, name)
35
+ if cmd is None:
36
+ continue
37
+ click.echo()
38
+ click.secho(f"━━━ scitex {group.name} {name} ━━━", fg="cyan", bold=True)
39
+ with click.Context(cmd, info_name=name, parent=parent_ctx) as sub_ctx:
40
+ click.echo(cmd.get_help(sub_ctx))
41
+ # Handle nested subgroups
42
+ if isinstance(cmd, click.Group):
43
+ for sub_name in sorted(cmd.list_commands(sub_ctx) or []):
44
+ sub_cmd = cmd.get_command(sub_ctx, sub_name)
45
+ if sub_cmd is None:
46
+ continue
47
+ click.echo()
48
+ click.secho(
49
+ f"━━━ scitex {group.name} {name} {sub_name} ━━━",
50
+ fg="cyan",
51
+ bold=True,
52
+ )
53
+ with click.Context(
54
+ sub_cmd, info_name=sub_name, parent=sub_ctx
55
+ ) as sub_sub_ctx:
56
+ click.echo(sub_cmd.get_help(sub_sub_ctx))
57
+
58
+
59
+ __all__ = ["cli", "print_help_recursive"]