scitex 2.7.3__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.
- scitex/__version__.py +1 -1
- scitex/dev/plt/__init__.py +0 -0
- scitex/dev/plt/plot_mpl_axhline.py +0 -0
- scitex/dev/plt/plot_mpl_axhspan.py +0 -0
- scitex/dev/plt/plot_mpl_axvline.py +0 -0
- scitex/dev/plt/plot_mpl_axvspan.py +0 -0
- scitex/dev/plt/plot_mpl_bar.py +0 -0
- scitex/dev/plt/plot_mpl_barh.py +0 -0
- scitex/dev/plt/plot_mpl_boxplot.py +0 -0
- scitex/dev/plt/plot_mpl_contour.py +0 -0
- scitex/dev/plt/plot_mpl_contourf.py +0 -0
- scitex/dev/plt/plot_mpl_errorbar.py +0 -0
- scitex/dev/plt/plot_mpl_eventplot.py +0 -0
- scitex/dev/plt/plot_mpl_fill.py +0 -0
- scitex/dev/plt/plot_mpl_fill_between.py +0 -0
- scitex/dev/plt/plot_mpl_hexbin.py +0 -0
- scitex/dev/plt/plot_mpl_hist.py +0 -0
- scitex/dev/plt/plot_mpl_hist2d.py +0 -0
- scitex/dev/plt/plot_mpl_imshow.py +0 -0
- scitex/dev/plt/plot_mpl_pcolormesh.py +0 -0
- scitex/dev/plt/plot_mpl_pie.py +0 -0
- scitex/dev/plt/plot_mpl_plot.py +0 -0
- scitex/dev/plt/plot_mpl_quiver.py +0 -0
- scitex/dev/plt/plot_mpl_scatter.py +0 -0
- scitex/dev/plt/plot_mpl_stackplot.py +0 -0
- scitex/dev/plt/plot_mpl_stem.py +0 -0
- scitex/dev/plt/plot_mpl_step.py +0 -0
- scitex/dev/plt/plot_mpl_violinplot.py +0 -0
- scitex/dev/plt/plot_sns_barplot.py +0 -0
- scitex/dev/plt/plot_sns_boxplot.py +0 -0
- scitex/dev/plt/plot_sns_heatmap.py +0 -0
- scitex/dev/plt/plot_sns_histplot.py +0 -0
- scitex/dev/plt/plot_sns_kdeplot.py +0 -0
- scitex/dev/plt/plot_sns_lineplot.py +0 -0
- scitex/dev/plt/plot_sns_scatterplot.py +0 -0
- scitex/dev/plt/plot_sns_stripplot.py +0 -0
- scitex/dev/plt/plot_sns_swarmplot.py +0 -0
- scitex/dev/plt/plot_sns_violinplot.py +0 -0
- scitex/dev/plt/plot_stx_bar.py +0 -0
- scitex/dev/plt/plot_stx_barh.py +0 -0
- scitex/dev/plt/plot_stx_box.py +0 -0
- scitex/dev/plt/plot_stx_boxplot.py +0 -0
- scitex/dev/plt/plot_stx_conf_mat.py +0 -0
- scitex/dev/plt/plot_stx_contour.py +0 -0
- scitex/dev/plt/plot_stx_ecdf.py +0 -0
- scitex/dev/plt/plot_stx_errorbar.py +0 -0
- scitex/dev/plt/plot_stx_fill_between.py +0 -0
- scitex/dev/plt/plot_stx_fillv.py +0 -0
- scitex/dev/plt/plot_stx_heatmap.py +0 -0
- scitex/dev/plt/plot_stx_image.py +0 -0
- scitex/dev/plt/plot_stx_imshow.py +0 -0
- scitex/dev/plt/plot_stx_joyplot.py +0 -0
- scitex/dev/plt/plot_stx_kde.py +0 -0
- scitex/dev/plt/plot_stx_line.py +0 -0
- scitex/dev/plt/plot_stx_mean_ci.py +0 -0
- scitex/dev/plt/plot_stx_mean_std.py +0 -0
- scitex/dev/plt/plot_stx_median_iqr.py +0 -0
- scitex/dev/plt/plot_stx_raster.py +0 -0
- scitex/dev/plt/plot_stx_rectangle.py +0 -0
- scitex/dev/plt/plot_stx_scatter.py +0 -0
- scitex/dev/plt/plot_stx_shaded_line.py +0 -0
- scitex/dev/plt/plot_stx_violin.py +0 -0
- scitex/dev/plt/plot_stx_violinplot.py +0 -0
- scitex/diagram/README.md +197 -0
- scitex/diagram/__init__.py +48 -0
- scitex/diagram/_compile.py +312 -0
- scitex/diagram/_diagram.py +355 -0
- scitex/diagram/_presets.py +173 -0
- scitex/diagram/_schema.py +182 -0
- scitex/diagram/_split.py +278 -0
- scitex/fig/editor/__init__.py +5 -2
- scitex/fig/editor/_dearpygui_editor.py +1 -1
- scitex/fig/editor/_mpl_editor.py +1 -1
- scitex/fig/editor/_qt_editor.py +1 -1
- scitex/fig/editor/_tkinter_editor.py +1 -1
- scitex/fig/editor/edit/__init__.py +50 -0
- scitex/fig/editor/edit/backend_detector.py +109 -0
- scitex/fig/editor/edit/bundle_resolver.py +240 -0
- scitex/fig/editor/edit/editor_launcher.py +239 -0
- scitex/fig/editor/edit/manual_handler.py +53 -0
- scitex/fig/editor/edit/panel_loader.py +232 -0
- scitex/fig/editor/edit/path_resolver.py +67 -0
- scitex/fig/editor/flask_editor/_bbox.py +23 -0
- scitex/fig/editor/flask_editor/_core.py +908 -103
- scitex/fig/editor/flask_editor/_renderer.py +74 -0
- scitex/fig/editor/flask_editor/static/css/base/reset.css +41 -0
- scitex/fig/editor/flask_editor/static/css/base/typography.css +16 -0
- scitex/fig/editor/flask_editor/static/css/base/variables.css +85 -0
- scitex/fig/editor/flask_editor/static/css/components/buttons.css +217 -0
- scitex/fig/editor/flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fig/editor/flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fig/editor/flask_editor/static/css/components/forms.css +112 -0
- scitex/fig/editor/flask_editor/static/css/components/modal.css +59 -0
- scitex/fig/editor/flask_editor/static/css/components/sections.css +212 -0
- scitex/fig/editor/flask_editor/static/css/features/canvas.css +176 -0
- scitex/fig/editor/flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fig/editor/flask_editor/static/css/features/loading.css +59 -0
- scitex/fig/editor/flask_editor/static/css/features/overlay.css +45 -0
- scitex/fig/editor/flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fig/editor/flask_editor/static/css/features/selection.css +101 -0
- scitex/fig/editor/flask_editor/static/css/features/statistics.css +138 -0
- scitex/fig/editor/flask_editor/static/css/index.css +31 -0
- scitex/fig/editor/flask_editor/static/css/layout/container.css +7 -0
- scitex/fig/editor/flask_editor/static/css/layout/controls.css +56 -0
- scitex/fig/editor/flask_editor/static/css/layout/preview.css +78 -0
- scitex/fig/editor/flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fig/editor/flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fig/editor/flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fig/editor/flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fig/editor/flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fig/editor/flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fig/editor/flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fig/editor/flask_editor/static/js/core/api.js +288 -0
- scitex/fig/editor/flask_editor/static/js/core/state.js +143 -0
- scitex/fig/editor/flask_editor/static/js/core/utils.js +245 -0
- scitex/fig/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fig/editor/flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fig/editor/flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fig/editor/flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fig/editor/flask_editor/static/js/editor/preview.js +293 -0
- scitex/fig/editor/flask_editor/static/js/main.js +426 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fig/editor/flask_editor/static/js/ui/controls.js +184 -0
- scitex/fig/editor/flask_editor/static/js/ui/download.js +57 -0
- scitex/fig/editor/flask_editor/static/js/ui/help.js +100 -0
- scitex/fig/editor/flask_editor/static/js/ui/theme.js +34 -0
- scitex/fig/editor/flask_editor/templates/__init__.py +95 -5
- scitex/fig/editor/flask_editor/templates/_html.py +27 -9
- scitex/fig/editor/flask_editor/templates/_scripts.py +1928 -131
- scitex/fig/editor/flask_editor/templates/_styles.py +363 -51
- scitex/fig/io/_bundle.py +97 -12
- scitex/io/__init__.py +12 -0
- scitex/io/_bundle.py +69 -10
- scitex/io/_zip_bundle.py +439 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +0 -0
- scitex/plt/io/_layered_bundle.py +0 -0
- scitex/schema/_plot.py +0 -0
- {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/METADATA +1 -1
- {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/RECORD +78 -22
- scitex/fig/editor/_edit.py +0 -751
- {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/WHEEL +0 -0
- {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/entry_points.txt +0 -0
- {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-12-14 (ywatanabe)"
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fig/editor/edit/panel_loader.py
|
|
5
|
+
|
|
6
|
+
"""Panel data loading for figure editor."""
|
|
7
|
+
|
|
8
|
+
import io
|
|
9
|
+
import json as json_module
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional, Union, Dict, Any
|
|
12
|
+
|
|
13
|
+
__all__ = ["load_panel_data"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_panel_data(panel_path: Union[Path, str], is_zip: bool = None) -> Optional[Dict[str, Any]]:
|
|
17
|
+
"""
|
|
18
|
+
Load panel data from either a .pltz.d directory or a .pltz zip file.
|
|
19
|
+
|
|
20
|
+
Handles both formats transparently using in-memory reading for zips.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
panel_path : Path or str
|
|
25
|
+
Path to .pltz.d directory or .pltz zip file
|
|
26
|
+
is_zip : bool, optional
|
|
27
|
+
If True, treat as zip file. If False, treat as directory.
|
|
28
|
+
If None, auto-detect based on path suffix and existence.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
dict or None
|
|
33
|
+
Dictionary with keys: metadata, csv_data, png_bytes, hitmap_bytes, img_size
|
|
34
|
+
For directories, also includes: json_path, png_path, hitmap_path
|
|
35
|
+
Returns None if panel cannot be loaded
|
|
36
|
+
"""
|
|
37
|
+
from PIL import Image
|
|
38
|
+
|
|
39
|
+
panel_path = Path(panel_path)
|
|
40
|
+
|
|
41
|
+
# Auto-detect if not specified
|
|
42
|
+
if is_zip is None:
|
|
43
|
+
spath = str(panel_path)
|
|
44
|
+
if spath.endswith('.pltz') and not spath.endswith('.pltz.d'):
|
|
45
|
+
is_zip = panel_path.is_file()
|
|
46
|
+
else:
|
|
47
|
+
is_zip = False
|
|
48
|
+
|
|
49
|
+
if is_zip:
|
|
50
|
+
return _load_from_zip(panel_path)
|
|
51
|
+
else:
|
|
52
|
+
return _load_from_directory(panel_path)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _load_from_zip(panel_path: Path) -> Optional[Dict[str, Any]]:
|
|
56
|
+
"""Load panel data from a .pltz zip file."""
|
|
57
|
+
from PIL import Image
|
|
58
|
+
from scitex.io._zip_bundle import ZipBundle
|
|
59
|
+
|
|
60
|
+
if not panel_path.exists():
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
with ZipBundle(panel_path, mode="r") as zb:
|
|
65
|
+
# Load spec.json for metadata
|
|
66
|
+
try:
|
|
67
|
+
metadata = zb.read_json("spec.json")
|
|
68
|
+
except FileNotFoundError:
|
|
69
|
+
metadata = {}
|
|
70
|
+
|
|
71
|
+
# Load style.json if exists
|
|
72
|
+
try:
|
|
73
|
+
style = zb.read_json("style.json")
|
|
74
|
+
metadata["style"] = style
|
|
75
|
+
except FileNotFoundError:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
# Find and read PNG
|
|
79
|
+
png_bytes = None
|
|
80
|
+
for name in zb.namelist():
|
|
81
|
+
if name.endswith('.png') and '_hitmap' not in name and '_overview' not in name:
|
|
82
|
+
if 'exports/' in name:
|
|
83
|
+
png_bytes = zb.read_bytes(name)
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
# If no PNG in exports/, try root level
|
|
87
|
+
if not png_bytes:
|
|
88
|
+
for name in zb.namelist():
|
|
89
|
+
if name.endswith('.png') and '_hitmap' not in name and '_overview' not in name:
|
|
90
|
+
png_bytes = zb.read_bytes(name)
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
# Get image size
|
|
94
|
+
img_size = None
|
|
95
|
+
if png_bytes:
|
|
96
|
+
img = Image.open(io.BytesIO(png_bytes))
|
|
97
|
+
img_size = {"width": img.size[0], "height": img.size[1]}
|
|
98
|
+
img.close()
|
|
99
|
+
|
|
100
|
+
# Find and read hitmap
|
|
101
|
+
hitmap_bytes = None
|
|
102
|
+
for name in zb.namelist():
|
|
103
|
+
if '_hitmap.png' in name:
|
|
104
|
+
hitmap_bytes = zb.read_bytes(name)
|
|
105
|
+
break
|
|
106
|
+
|
|
107
|
+
# Load geometry_px.json if available
|
|
108
|
+
geometry_data = None
|
|
109
|
+
try:
|
|
110
|
+
geometry_data = zb.read_json("cache/geometry_px.json")
|
|
111
|
+
except FileNotFoundError:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"metadata": metadata,
|
|
116
|
+
"png_bytes": png_bytes,
|
|
117
|
+
"hitmap_bytes": hitmap_bytes,
|
|
118
|
+
"img_size": img_size,
|
|
119
|
+
"geometry_data": geometry_data,
|
|
120
|
+
"is_zip": True,
|
|
121
|
+
}
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print(f"Error loading panel zip {panel_path}: {e}")
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _load_from_directory(panel_path: Path) -> Optional[Dict[str, Any]]:
|
|
128
|
+
"""Load panel data from a .pltz.d directory."""
|
|
129
|
+
import scitex as stx
|
|
130
|
+
|
|
131
|
+
panel_dir = panel_path
|
|
132
|
+
if not panel_dir.exists():
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
# Check for layered vs legacy format
|
|
136
|
+
spec_path = panel_dir / "spec.json"
|
|
137
|
+
if spec_path.exists():
|
|
138
|
+
return _load_layered_directory(panel_dir)
|
|
139
|
+
else:
|
|
140
|
+
return _load_legacy_directory(panel_dir)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _load_layered_directory(panel_dir: Path) -> Dict[str, Any]:
|
|
144
|
+
"""Load panel data from layered format directory."""
|
|
145
|
+
import scitex as stx
|
|
146
|
+
from scitex.plt.io import load_layered_pltz_bundle
|
|
147
|
+
|
|
148
|
+
bundle_data = load_layered_pltz_bundle(panel_dir)
|
|
149
|
+
metadata = bundle_data.get("merged", {})
|
|
150
|
+
|
|
151
|
+
# Find CSV
|
|
152
|
+
csv_data = None
|
|
153
|
+
for f in panel_dir.glob("*.csv"):
|
|
154
|
+
csv_data = stx.io.load(f)
|
|
155
|
+
break
|
|
156
|
+
|
|
157
|
+
# Find exports - prefer PNG over SVG (PIL can't open SVG)
|
|
158
|
+
png_path = None
|
|
159
|
+
svg_path = None
|
|
160
|
+
hitmap_path = None
|
|
161
|
+
exports_dir = panel_dir / "exports"
|
|
162
|
+
if exports_dir.exists():
|
|
163
|
+
for f in exports_dir.iterdir():
|
|
164
|
+
name = f.name
|
|
165
|
+
if name.endswith('_hitmap.png'):
|
|
166
|
+
hitmap_path = f
|
|
167
|
+
elif name.endswith('.png') and '_hitmap' not in name and '_overview' not in name:
|
|
168
|
+
png_path = f
|
|
169
|
+
elif name.endswith('.svg') and '_hitmap' not in name and svg_path is None:
|
|
170
|
+
svg_path = f
|
|
171
|
+
|
|
172
|
+
if png_path is None:
|
|
173
|
+
png_path = svg_path
|
|
174
|
+
|
|
175
|
+
# Load geometry_px.json if available
|
|
176
|
+
geometry_data = None
|
|
177
|
+
geometry_path = panel_dir / "cache" / "geometry_px.json"
|
|
178
|
+
if geometry_path.exists():
|
|
179
|
+
with open(geometry_path) as f:
|
|
180
|
+
geometry_data = json_module.load(f)
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
"json_path": panel_dir / "spec.json",
|
|
184
|
+
"metadata": metadata,
|
|
185
|
+
"csv_data": csv_data,
|
|
186
|
+
"png_path": png_path,
|
|
187
|
+
"hitmap_path": hitmap_path,
|
|
188
|
+
"geometry_data": geometry_data,
|
|
189
|
+
"is_zip": False,
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _load_legacy_directory(panel_dir: Path) -> Optional[Dict[str, Any]]:
|
|
194
|
+
"""Load panel data from legacy format directory."""
|
|
195
|
+
import scitex as stx
|
|
196
|
+
|
|
197
|
+
json_path = None
|
|
198
|
+
csv_data = None
|
|
199
|
+
png_path = None
|
|
200
|
+
hitmap_path = None
|
|
201
|
+
|
|
202
|
+
for f in panel_dir.iterdir():
|
|
203
|
+
name = f.name
|
|
204
|
+
if name.endswith('.json') and not name.endswith('.manual.json'):
|
|
205
|
+
json_path = f
|
|
206
|
+
elif name.endswith('.csv'):
|
|
207
|
+
csv_data = stx.io.load(f)
|
|
208
|
+
elif name.endswith('_hitmap.png'):
|
|
209
|
+
hitmap_path = f
|
|
210
|
+
elif name.endswith('.svg') and '_hitmap' not in name:
|
|
211
|
+
png_path = f
|
|
212
|
+
elif name.endswith('.png') and '_hitmap' not in name and '_overview' not in name:
|
|
213
|
+
if png_path is None:
|
|
214
|
+
png_path = f
|
|
215
|
+
|
|
216
|
+
if json_path is None:
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
with open(json_path, 'r') as f:
|
|
220
|
+
metadata = json_module.load(f)
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
"json_path": json_path,
|
|
224
|
+
"metadata": metadata,
|
|
225
|
+
"csv_data": csv_data,
|
|
226
|
+
"png_path": png_path,
|
|
227
|
+
"hitmap_path": hitmap_path,
|
|
228
|
+
"is_zip": False,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# EOF
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-12-14 (ywatanabe)"
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fig/editor/edit/path_resolver.py
|
|
5
|
+
|
|
6
|
+
"""Basic path resolution for figure files."""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional, Tuple
|
|
10
|
+
|
|
11
|
+
__all__ = ["resolve_figure_paths"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def resolve_figure_paths(path: Path) -> Tuple[Path, Optional[Path], Optional[Path]]:
|
|
15
|
+
"""
|
|
16
|
+
Resolve JSON, CSV, and PNG paths from any input file path.
|
|
17
|
+
|
|
18
|
+
Handles two patterns:
|
|
19
|
+
1. Flat (sibling): path/to/figure.{json,csv,png}
|
|
20
|
+
2. Organized (subdirs): path/to/{json,csv,png}/figure.{ext}
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
path : Path
|
|
25
|
+
Input path (can be JSON, CSV, or PNG)
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
tuple
|
|
30
|
+
(json_path, csv_path, png_path) - csv_path/png_path may be None if not found
|
|
31
|
+
"""
|
|
32
|
+
path = Path(path)
|
|
33
|
+
stem = path.stem
|
|
34
|
+
parent = path.parent
|
|
35
|
+
|
|
36
|
+
# Check if this is organized pattern (parent is json/, csv/, png/)
|
|
37
|
+
if parent.name in ("json", "csv", "png"):
|
|
38
|
+
base_dir = parent.parent
|
|
39
|
+
json_path = base_dir / "json" / f"{stem}.json"
|
|
40
|
+
csv_path = base_dir / "csv" / f"{stem}.csv"
|
|
41
|
+
png_path = base_dir / "png" / f"{stem}.png"
|
|
42
|
+
else:
|
|
43
|
+
# Flat pattern - sibling files
|
|
44
|
+
json_path = parent / f"{stem}.json"
|
|
45
|
+
csv_path = parent / f"{stem}.csv"
|
|
46
|
+
png_path = parent / f"{stem}.png"
|
|
47
|
+
|
|
48
|
+
# If input was .manual.json, get base json
|
|
49
|
+
if stem.endswith(".manual"):
|
|
50
|
+
base_stem = stem[:-7] # Remove '.manual'
|
|
51
|
+
if parent.name == "json":
|
|
52
|
+
json_path = parent / f"{base_stem}.json"
|
|
53
|
+
csv_path = parent.parent / "csv" / f"{base_stem}.csv"
|
|
54
|
+
png_path = parent.parent / "png" / f"{base_stem}.png"
|
|
55
|
+
else:
|
|
56
|
+
json_path = parent / f"{base_stem}.json"
|
|
57
|
+
csv_path = parent / f"{base_stem}.csv"
|
|
58
|
+
png_path = parent / f"{base_stem}.png"
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
json_path,
|
|
62
|
+
csv_path if csv_path.exists() else None,
|
|
63
|
+
png_path if png_path.exists() else None,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# EOF
|
|
@@ -290,6 +290,29 @@ def extract_bboxes_multi(
|
|
|
290
290
|
legend = ax.get_legend()
|
|
291
291
|
if legend:
|
|
292
292
|
get_element_bbox(legend, "legend", ax_id, ax)
|
|
293
|
+
# Add element_type for drag detection
|
|
294
|
+
if f"{ax_id}_legend" in bboxes:
|
|
295
|
+
bboxes[f"{ax_id}_legend"]["element_type"] = "legend"
|
|
296
|
+
bboxes[f"{ax_id}_legend"]["draggable"] = True
|
|
297
|
+
|
|
298
|
+
# Get panel letter (text annotations like A, B, C)
|
|
299
|
+
import re
|
|
300
|
+
panel_letter_pattern = re.compile(r'^[A-Z]\.?$|^\([A-Za-z]\)$')
|
|
301
|
+
for idx, text_artist in enumerate(ax.texts):
|
|
302
|
+
text_content = text_artist.get_text().strip()
|
|
303
|
+
if text_content and panel_letter_pattern.match(text_content):
|
|
304
|
+
name = f"panel_letter_{text_content.replace('.', '').replace('(', '').replace(')', '')}"
|
|
305
|
+
get_element_bbox(text_artist, name, ax_id, ax)
|
|
306
|
+
full_name = f"{ax_id}_{name}"
|
|
307
|
+
if full_name in bboxes:
|
|
308
|
+
bboxes[full_name]["element_type"] = "panel_letter"
|
|
309
|
+
bboxes[full_name]["draggable"] = True
|
|
310
|
+
bboxes[full_name]["text"] = text_content
|
|
311
|
+
# Get position in axes coordinates (0-1)
|
|
312
|
+
pos = text_artist.get_position()
|
|
313
|
+
transform = text_artist.get_transform()
|
|
314
|
+
if transform == ax.transAxes:
|
|
315
|
+
bboxes[full_name]["axes_position"] = {"x": pos[0], "y": pos[1]}
|
|
293
316
|
|
|
294
317
|
# Get trace (line) bboxes
|
|
295
318
|
_extract_trace_bboxes_for_axis(
|