batplot 1.8.2__py3-none-any.whl → 1.8.4__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.
- batplot/__init__.py +1 -1
- batplot/batch.py +23 -0
- batplot/batplot.py +58 -8
- batplot/cli.py +14 -0
- batplot/cpc_interactive.py +15 -3
- batplot/electrochem_interactive.py +20 -4
- batplot/interactive.py +9 -15
- batplot/session.py +157 -0
- batplot/style.py +21 -2
- {batplot-1.8.2.dist-info → batplot-1.8.4.dist-info}/METADATA +4 -9
- {batplot-1.8.2.dist-info → batplot-1.8.4.dist-info}/RECORD +15 -15
- {batplot-1.8.2.dist-info → batplot-1.8.4.dist-info}/WHEEL +0 -0
- {batplot-1.8.2.dist-info → batplot-1.8.4.dist-info}/entry_points.txt +0 -0
- {batplot-1.8.2.dist-info → batplot-1.8.4.dist-info}/licenses/LICENSE +0 -0
- {batplot-1.8.2.dist-info → batplot-1.8.4.dist-info}/top_level.txt +0 -0
batplot/__init__.py
CHANGED
batplot/batch.py
CHANGED
|
@@ -157,6 +157,18 @@ def _apply_xy_style(fig, ax, cfg: dict):
|
|
|
157
157
|
except Exception:
|
|
158
158
|
pass
|
|
159
159
|
|
|
160
|
+
# Enforce compatibility between style/geom ro state and current figure ro state.
|
|
161
|
+
# Styles saved from a plot using --ro (swapped x/y) must not be applied to a non-ro plot, and vice versa.
|
|
162
|
+
file_ro = bool(cfg.get('ro_active', False))
|
|
163
|
+
current_ro = bool(getattr(fig, '_ro_active', False))
|
|
164
|
+
if file_ro != current_ro:
|
|
165
|
+
if file_ro:
|
|
166
|
+
print("Warning: XY style/geometry file was saved with --ro (swapped x/y axes); batch plot is not using --ro.")
|
|
167
|
+
else:
|
|
168
|
+
print("Warning: XY style/geometry file was saved without --ro; batch plot is treated as non-ro.")
|
|
169
|
+
print("Skipping style/geometry in batch mode to avoid corrupting axis orientation.")
|
|
170
|
+
return
|
|
171
|
+
|
|
160
172
|
# Apply WASD state (tick visibility)
|
|
161
173
|
wasd_cfg = cfg.get('wasd_state', {})
|
|
162
174
|
if wasd_cfg:
|
|
@@ -223,6 +235,17 @@ def _apply_ec_style(fig, ax, cfg: dict):
|
|
|
223
235
|
cfg: Style configuration dictionary
|
|
224
236
|
"""
|
|
225
237
|
try:
|
|
238
|
+
# Enforce compatibility between style/geom ro state and current figure ro state.
|
|
239
|
+
file_ro = bool(cfg.get('ro_active', False))
|
|
240
|
+
current_ro = bool(getattr(fig, '_ro_active', False))
|
|
241
|
+
if file_ro != current_ro:
|
|
242
|
+
if file_ro:
|
|
243
|
+
print("Warning: EC style/geometry file was saved with --ro (swapped x/y axes); batch EC plot is not using --ro.")
|
|
244
|
+
else:
|
|
245
|
+
print("Warning: EC style/geometry file was saved without --ro; batch EC plot is treated as non-ro.")
|
|
246
|
+
print("Skipping EC style/geometry in batch mode to avoid corrupting axis orientation.")
|
|
247
|
+
return
|
|
248
|
+
|
|
226
249
|
# Apply fonts
|
|
227
250
|
font_cfg = cfg.get('font', {})
|
|
228
251
|
if font_cfg:
|
batplot/batplot.py
CHANGED
|
@@ -507,6 +507,11 @@ def batplot_main() -> int:
|
|
|
507
507
|
except Exception:
|
|
508
508
|
pass
|
|
509
509
|
_plt.show(block=False)
|
|
510
|
+
# Track whether data axes were swapped via --ro for this EC figure
|
|
511
|
+
try:
|
|
512
|
+
fig._ro_active = bool(getattr(args, "ro", False))
|
|
513
|
+
except Exception:
|
|
514
|
+
pass
|
|
510
515
|
try:
|
|
511
516
|
fig._bp_source_paths = [_os.path.abspath(ec_file)]
|
|
512
517
|
except Exception:
|
|
@@ -1785,6 +1790,38 @@ def batplot_main() -> int:
|
|
|
1785
1790
|
if not isinstance(sess, dict) or 'version' not in sess:
|
|
1786
1791
|
print("Not a valid batplot session file.")
|
|
1787
1792
|
exit(1)
|
|
1793
|
+
except ModuleNotFoundError as e:
|
|
1794
|
+
# Handle numpy._core and other module import errors
|
|
1795
|
+
if '_core' in str(e) or 'numpy' in str(e).lower():
|
|
1796
|
+
# Try to extract version info before the error
|
|
1797
|
+
from .session import _try_extract_version_from_pickle, _get_current_numpy_version
|
|
1798
|
+
saved_versions = _try_extract_version_from_pickle(sess_path)
|
|
1799
|
+
current_numpy = _get_current_numpy_version()
|
|
1800
|
+
|
|
1801
|
+
saved_numpy = saved_versions.get('numpy', 'unknown')
|
|
1802
|
+
|
|
1803
|
+
print(f"\nERROR: NumPy version mismatch detected when loading: {sess_path}")
|
|
1804
|
+
print("This session was saved with a different NumPy version.")
|
|
1805
|
+
print()
|
|
1806
|
+
print(f"Session was saved with: NumPy {saved_numpy}")
|
|
1807
|
+
print(f"Currently installed: NumPy {current_numpy}")
|
|
1808
|
+
print()
|
|
1809
|
+
print("The error 'No module named numpy._core' indicates:")
|
|
1810
|
+
print(" - Session saved with NumPy 2.0+ but loading with NumPy <2.0, OR")
|
|
1811
|
+
print(" - Session saved with NumPy <2.0 but loading with NumPy 2.0+")
|
|
1812
|
+
print()
|
|
1813
|
+
print("Solutions:")
|
|
1814
|
+
if saved_numpy != 'unknown':
|
|
1815
|
+
print(f" 1. Install matching version: pip install 'numpy=={saved_numpy}'")
|
|
1816
|
+
else:
|
|
1817
|
+
print(" 1. Try installing NumPy <2.0: pip install 'numpy<2.0'")
|
|
1818
|
+
print(" OR try installing NumPy 2.0+: pip install 'numpy>=2.0'")
|
|
1819
|
+
print(" 2. Recreate the session from original data files")
|
|
1820
|
+
else:
|
|
1821
|
+
print(f"\nERROR: Module import error when loading: {sess_path}")
|
|
1822
|
+
print(f"Error: {e}")
|
|
1823
|
+
print("This usually indicates a package version mismatch.")
|
|
1824
|
+
exit(1)
|
|
1788
1825
|
except Exception as e:
|
|
1789
1826
|
print(f"Failed to load session: {e}")
|
|
1790
1827
|
exit(1)
|
|
@@ -1882,6 +1919,11 @@ def batplot_main() -> int:
|
|
|
1882
1919
|
# Reconstruct minimal state and go to interactive if requested
|
|
1883
1920
|
plt.ion() if args.interactive else None
|
|
1884
1921
|
fig, ax = plt.subplots(figsize=(8,6))
|
|
1922
|
+
# Restore ro flag from session (if present) so style/geom imports can enforce compatibility
|
|
1923
|
+
try:
|
|
1924
|
+
fig._ro_active = bool(sess.get('ro_active', False))
|
|
1925
|
+
except Exception:
|
|
1926
|
+
pass
|
|
1885
1927
|
y_data_list = []
|
|
1886
1928
|
x_data_list = []
|
|
1887
1929
|
labels_list = []
|
|
@@ -2973,19 +3015,21 @@ def batplot_main() -> int:
|
|
|
2973
3015
|
offset += increment
|
|
2974
3016
|
|
|
2975
3017
|
# ---- Plot curve ----
|
|
2976
|
-
# Swap x and y if --ro flag is set
|
|
3018
|
+
# Swap x and y if --ro flag is set (and keep lists aligned once)
|
|
2977
3019
|
if getattr(args, 'ro', False):
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
x_data_list.append(y_plot_offset)
|
|
3020
|
+
x_plotted = y_plot_offset # goes on x-axis when rotated
|
|
3021
|
+
y_plotted = x_plot # goes on y-axis when rotated
|
|
2981
3022
|
else:
|
|
3023
|
+
x_plotted = x_plot
|
|
3024
|
+
y_plotted = y_plot_offset
|
|
2982
3025
|
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
3026
|
+
ax.plot(x_plotted, y_plotted, "-", lw=1, alpha=0.8)
|
|
3027
|
+
x_data_list.append(x_plotted)
|
|
3028
|
+
y_data_list.append(y_plotted.copy())
|
|
2986
3029
|
labels_list.append(label)
|
|
2987
3030
|
# Store current normalized (subset) (used by rearrange logic)
|
|
2988
|
-
orig_y.
|
|
3031
|
+
# Keep orig_y aligned with the plotted y data to avoid length mismatch on undo/relabel.
|
|
3032
|
+
orig_y.append(y_plotted.copy())
|
|
2989
3033
|
|
|
2990
3034
|
# ---------------- Force axis to fit all data before labels ----------------
|
|
2991
3035
|
ax.relim()
|
|
@@ -3498,6 +3542,12 @@ def batplot_main() -> int:
|
|
|
3498
3542
|
fig.set_tight_layout(False)
|
|
3499
3543
|
except Exception:
|
|
3500
3544
|
pass
|
|
3545
|
+
|
|
3546
|
+
# Track whether data axes were swapped via --ro for this figure
|
|
3547
|
+
try:
|
|
3548
|
+
fig._ro_active = bool(getattr(args, "ro", False))
|
|
3549
|
+
except Exception:
|
|
3550
|
+
pass
|
|
3501
3551
|
|
|
3502
3552
|
# Build CIF globals dict for explicit passing
|
|
3503
3553
|
cif_globals = {
|
batplot/cli.py
CHANGED
|
@@ -59,6 +59,20 @@ def main(argv: Optional[list] = None) -> int:
|
|
|
59
59
|
>>> main()
|
|
60
60
|
0
|
|
61
61
|
"""
|
|
62
|
+
# ====================================================================
|
|
63
|
+
# STEP 0: PYTHON VERSION CHECK
|
|
64
|
+
# ====================================================================
|
|
65
|
+
# Check if Python version is 3.13 (required for batplot)
|
|
66
|
+
# ====================================================================
|
|
67
|
+
if sys.version_info.major != 3 or sys.version_info.minor != 13:
|
|
68
|
+
current_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
69
|
+
print(f"\n⚠️ WARNING: Python version mismatch detected!")
|
|
70
|
+
print(f" batplot requires Python 3.13")
|
|
71
|
+
print(f" Currently running: Python {current_version}")
|
|
72
|
+
print(f"\n This may cause compatibility issues.")
|
|
73
|
+
print(f" Please install Python 3.13 and use it to run batplot.")
|
|
74
|
+
print(f" Continuing anyway, but expect potential issues...\n")
|
|
75
|
+
|
|
62
76
|
# ====================================================================
|
|
63
77
|
# STEP 1: VERSION CHECK (NON-BLOCKING)
|
|
64
78
|
# ====================================================================
|
batplot/cpc_interactive.py
CHANGED
|
@@ -564,6 +564,8 @@ def _style_snapshot(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_data=Non
|
|
|
564
564
|
'frame_size': [frame_w_in, frame_h_in],
|
|
565
565
|
'axes_fraction': [ax_bbox.x0, ax_bbox.y0, ax_bbox.width, ax_bbox.height]
|
|
566
566
|
},
|
|
567
|
+
# Track whether data axes were swapped via --ro when this style was saved
|
|
568
|
+
'ro_active': bool(getattr(fig, '_ro_active', False)),
|
|
567
569
|
'font': {'family': fam0, 'size': fsize},
|
|
568
570
|
'legend': {
|
|
569
571
|
'visible': legend_visible,
|
|
@@ -1533,9 +1535,8 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
1533
1535
|
ax2.tick_params(axis='y', which='minor', right=True, labelright=False)
|
|
1534
1536
|
else:
|
|
1535
1537
|
ax2.tick_params(axis='y', which='minor', right=False, labelright=False)
|
|
1536
|
-
#
|
|
1537
|
-
|
|
1538
|
-
_ui_position_left_ylabel(ax, fig, tick_state)
|
|
1538
|
+
# Note: Do NOT call position functions during undo restore as it causes title drift
|
|
1539
|
+
# Title offsets are already restored from snapshot in restore_state()
|
|
1539
1540
|
try:
|
|
1540
1541
|
for spine_name, color in getattr(fig, '_cpc_spine_colors', {}).items():
|
|
1541
1542
|
_set_spine_color(spine_name, color)
|
|
@@ -2683,6 +2684,17 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
2683
2684
|
kind = cfg.get('kind', '')
|
|
2684
2685
|
if kind not in ('cpc_style', 'cpc_style_geom'):
|
|
2685
2686
|
print("Not a CPC style file."); _print_menu(); continue
|
|
2687
|
+
|
|
2688
|
+
# Enforce compatibility between style/geom ro state and current figure ro state
|
|
2689
|
+
file_ro = bool(cfg.get('ro_active', False))
|
|
2690
|
+
current_ro = bool(getattr(fig, '_ro_active', False))
|
|
2691
|
+
if file_ro != current_ro:
|
|
2692
|
+
if file_ro:
|
|
2693
|
+
print("Warning: Style/geometry file was saved with --ro (swapped x/y axes); current plot is not using --ro.")
|
|
2694
|
+
else:
|
|
2695
|
+
print("Warning: Style/geometry file was saved without --ro; current plot was created with --ro.")
|
|
2696
|
+
print("Not applying CPC style/geometry to avoid corrupting axis orientation.")
|
|
2697
|
+
_print_menu(); continue
|
|
2686
2698
|
|
|
2687
2699
|
has_geometry = (kind == 'cpc_style_geom' and 'geometry' in cfg)
|
|
2688
2700
|
|
|
@@ -1551,10 +1551,8 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1551
1551
|
ax._right_ylabel_manual_offset_y_pts = 0.0
|
|
1552
1552
|
ax._top_xlabel_on = bool(snap.get('titles',{}).get('top_x', False))
|
|
1553
1553
|
ax._right_ylabel_on = bool(snap.get('titles',{}).get('right_y', False))
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
_ui_position_left_ylabel(ax, fig, tick_state)
|
|
1557
|
-
_ui_position_right_ylabel(ax, fig, tick_state)
|
|
1554
|
+
# Note: Do NOT call position functions during undo restore as it causes title drift
|
|
1555
|
+
# Title offsets are already restored from snapshot above
|
|
1558
1556
|
except Exception:
|
|
1559
1557
|
pass
|
|
1560
1558
|
# Restore labelpads (for title positioning)
|
|
@@ -2091,6 +2089,18 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2091
2089
|
print("Not an EC style file.")
|
|
2092
2090
|
_print_menu(len(all_cycles), is_dqdv)
|
|
2093
2091
|
continue
|
|
2092
|
+
|
|
2093
|
+
# Enforce compatibility between style/geom ro state and current figure ro state
|
|
2094
|
+
file_ro = bool(cfg.get('ro_active', False))
|
|
2095
|
+
current_ro = bool(getattr(fig, '_ro_active', False))
|
|
2096
|
+
if file_ro != current_ro:
|
|
2097
|
+
if file_ro:
|
|
2098
|
+
print("Warning: EC style/geometry file was saved with --ro (swapped x/y axes); current plot is not using --ro.")
|
|
2099
|
+
else:
|
|
2100
|
+
print("Warning: EC style/geometry file was saved without --ro; current plot was created with --ro.")
|
|
2101
|
+
print("Not applying EC style/geometry to avoid corrupting axis orientation.")
|
|
2102
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
2103
|
+
continue
|
|
2094
2104
|
|
|
2095
2105
|
has_geometry = (kind == 'ec_style_geom' and 'geometry' in cfg)
|
|
2096
2106
|
|
|
@@ -4231,6 +4241,8 @@ def _get_style_snapshot(fig, ax, cycle_lines: Dict, tick_state: Dict) -> Dict:
|
|
|
4231
4241
|
'curve_markers': curve_marker_props,
|
|
4232
4242
|
'rotation_angle': getattr(fig, '_ec_rotation_angle', 0),
|
|
4233
4243
|
'cycle_styles': cycle_styles,
|
|
4244
|
+
# Track whether data axes were swapped via --ro when this style was saved
|
|
4245
|
+
'ro_active': bool(getattr(fig, '_ro_active', False)),
|
|
4234
4246
|
}
|
|
4235
4247
|
|
|
4236
4248
|
|
|
@@ -4322,6 +4334,10 @@ def _print_style_snapshot(cfg: Dict):
|
|
|
4322
4334
|
if rotation_angle != 0:
|
|
4323
4335
|
print(f"Rotation angle: {rotation_angle}°")
|
|
4324
4336
|
|
|
4337
|
+
# ro / axis-swap state
|
|
4338
|
+
ro_active = bool(cfg.get('ro_active', False))
|
|
4339
|
+
print(f"Data axes swapped via --ro: {'YES' if ro_active else 'no'}")
|
|
4340
|
+
|
|
4325
4341
|
# Per-side matrix summary (spine, major, minor, labels, title)
|
|
4326
4342
|
def _onoff(v):
|
|
4327
4343
|
return 'ON ' if bool(v) else 'off'
|
batplot/interactive.py
CHANGED
|
@@ -1218,8 +1218,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1218
1218
|
fig.set_size_inches(snap["fig_size"][0], snap["fig_size"][1], forward=True)
|
|
1219
1219
|
except Exception:
|
|
1220
1220
|
pass
|
|
1221
|
-
|
|
1222
|
-
print("(Canvas fixed) Ignoring undo figure size restore.")
|
|
1221
|
+
# No message needed - canvas size is managed by system
|
|
1223
1222
|
# Don't restore DPI from undo - use system default to avoid display-dependent issues
|
|
1224
1223
|
|
|
1225
1224
|
# Restore axes (plot frame) via stored bbox if present
|
|
@@ -1287,15 +1286,9 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1287
1286
|
position_right_ylabel()
|
|
1288
1287
|
except Exception:
|
|
1289
1288
|
pass
|
|
1290
|
-
#
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
except Exception:
|
|
1294
|
-
pass
|
|
1295
|
-
try:
|
|
1296
|
-
position_left_ylabel()
|
|
1297
|
-
except Exception:
|
|
1298
|
-
pass
|
|
1289
|
+
# Note: Do NOT call position_bottom_xlabel() / position_left_ylabel() here
|
|
1290
|
+
# as it causes title drift when combined with fig.canvas.draw() below.
|
|
1291
|
+
# Title offsets are already restored from snapshot above.
|
|
1299
1292
|
|
|
1300
1293
|
# Spines (linewidth, color, visibility)
|
|
1301
1294
|
for name, spec in snap.get("spines", {}).items():
|
|
@@ -1737,7 +1730,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1737
1730
|
show_cif_titles=(bool(getattr(_bp,'show_cif_titles', True)) if _bp is not None else True),
|
|
1738
1731
|
skip_confirm=skip_confirm,
|
|
1739
1732
|
)
|
|
1740
|
-
|
|
1733
|
+
# Message already printed by dump_session
|
|
1741
1734
|
fig._last_session_save_path = target_path
|
|
1742
1735
|
continue
|
|
1743
1736
|
else:
|
|
@@ -1777,7 +1770,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1777
1770
|
show_cif_titles=(bool(getattr(_bp,'show_cif_titles', True)) if _bp is not None else True),
|
|
1778
1771
|
skip_confirm=skip_confirm,
|
|
1779
1772
|
)
|
|
1780
|
-
|
|
1773
|
+
# Message already printed by dump_session
|
|
1781
1774
|
fig._last_session_save_path = target_path
|
|
1782
1775
|
except Exception as e:
|
|
1783
1776
|
print(f"Error saving session: {e}")
|
|
@@ -2697,10 +2690,11 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2697
2690
|
eps = abs(y_min)*1e-6 if y_min != 0 else 1e-6
|
|
2698
2691
|
y_min -= eps
|
|
2699
2692
|
y_max += eps
|
|
2700
|
-
|
|
2693
|
+
ax.set_ylim(y_min, y_max)
|
|
2701
2694
|
update_labels(ax, y_data_list, label_text_objects, args.stack, getattr(fig, '_stack_label_at_bottom', False))
|
|
2702
2695
|
fig.canvas.draw_idle()
|
|
2703
|
-
|
|
2696
|
+
ymin, ymax = ax.get_ylim()
|
|
2697
|
+
print(f"Y range set to ({float(ymin)}, {float(ymax)})")
|
|
2704
2698
|
except Exception as e:
|
|
2705
2699
|
print(f"Error setting Y-axis range: {e}")
|
|
2706
2700
|
elif key == 'd': # <-- DELTA / OFFSET HANDLER (now only reachable if not args.stack)
|
batplot/session.py
CHANGED
|
@@ -41,6 +41,68 @@ from .utils import _confirm_overwrite
|
|
|
41
41
|
from .color_utils import ensure_colormap
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def _try_extract_version_from_pickle(filename: str) -> Dict[str, str]:
|
|
45
|
+
"""Try to extract package_versions from a pickle file even if it fails to fully load.
|
|
46
|
+
|
|
47
|
+
Note: This may not work if pickle.load() fails completely due to missing modules.
|
|
48
|
+
In that case, we can't extract version info, but we can still show current version.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
dict with package versions, or empty dict if extraction fails
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
with open(filename, 'rb') as f:
|
|
55
|
+
# Try to load the pickle
|
|
56
|
+
# This will fail if numpy._core is missing, but we try anyway
|
|
57
|
+
sess = pickle.load(f)
|
|
58
|
+
if isinstance(sess, dict):
|
|
59
|
+
return sess.get('package_versions', {})
|
|
60
|
+
except Exception:
|
|
61
|
+
# If loading fails completely (e.g., ModuleNotFoundError for numpy._core),
|
|
62
|
+
# we can't extract version info. This is expected in version mismatch cases.
|
|
63
|
+
pass
|
|
64
|
+
return {}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _get_current_numpy_version() -> str:
|
|
68
|
+
"""Get current numpy version, even if import fails.
|
|
69
|
+
|
|
70
|
+
Tries multiple methods:
|
|
71
|
+
1. Direct import (fastest)
|
|
72
|
+
2. pip show (works even if import fails)
|
|
73
|
+
3. Returns 'unknown' if all fail
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Version string or 'unknown'
|
|
77
|
+
"""
|
|
78
|
+
# Method 1: Try direct import
|
|
79
|
+
try:
|
|
80
|
+
import numpy
|
|
81
|
+
return numpy.__version__
|
|
82
|
+
except Exception:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
# Method 2: Try pip show
|
|
86
|
+
try:
|
|
87
|
+
import subprocess
|
|
88
|
+
import sys
|
|
89
|
+
result = subprocess.run(
|
|
90
|
+
[sys.executable, '-m', 'pip', 'show', 'numpy'],
|
|
91
|
+
capture_output=True,
|
|
92
|
+
text=True,
|
|
93
|
+
timeout=5,
|
|
94
|
+
check=False
|
|
95
|
+
)
|
|
96
|
+
if result.returncode == 0:
|
|
97
|
+
for line in result.stdout.split('\n'):
|
|
98
|
+
if line.startswith('Version:'):
|
|
99
|
+
return line.split(':', 1)[1].strip()
|
|
100
|
+
except Exception:
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
return 'unknown'
|
|
104
|
+
|
|
105
|
+
|
|
44
106
|
def _current_tick_width(axis_obj, which: str):
|
|
45
107
|
"""
|
|
46
108
|
Return the configured tick width for the given X/Y axis.
|
|
@@ -507,6 +569,8 @@ def dump_session(
|
|
|
507
569
|
}
|
|
508
570
|
# Save curve names visibility
|
|
509
571
|
sess['curve_names_visible'] = bool(getattr(fig, '_curve_names_visible', True))
|
|
572
|
+
# Save whether data were plotted with swapped axes via --ro
|
|
573
|
+
sess['ro_active'] = bool(getattr(fig, '_ro_active', False))
|
|
510
574
|
# Save stack/legend anchor preferences
|
|
511
575
|
sess['stack_label_at_bottom'] = bool(getattr(fig, '_stack_label_at_bottom', False))
|
|
512
576
|
sess['label_anchor_left'] = bool(getattr(fig, '_label_anchor_left', False))
|
|
@@ -819,6 +883,37 @@ def load_operando_session(filename: str):
|
|
|
819
883
|
try:
|
|
820
884
|
with open(filename, 'rb') as f:
|
|
821
885
|
sess = pickle.load(f)
|
|
886
|
+
except ModuleNotFoundError as e:
|
|
887
|
+
# Handle numpy._core and other module import errors
|
|
888
|
+
if '_core' in str(e) or 'numpy' in str(e).lower():
|
|
889
|
+
# Try to extract version info before the error
|
|
890
|
+
saved_versions = _try_extract_version_from_pickle(filename)
|
|
891
|
+
current_numpy = _get_current_numpy_version()
|
|
892
|
+
|
|
893
|
+
saved_numpy = saved_versions.get('numpy', 'unknown')
|
|
894
|
+
|
|
895
|
+
print(f"\nERROR: NumPy version mismatch detected when loading: {filename}")
|
|
896
|
+
print("This session was saved with a different NumPy version.")
|
|
897
|
+
print()
|
|
898
|
+
print(f"Session was saved with: NumPy {saved_numpy}")
|
|
899
|
+
print(f"Currently installed: NumPy {current_numpy}")
|
|
900
|
+
print()
|
|
901
|
+
print("The error 'No module named numpy._core' indicates:")
|
|
902
|
+
print(" - Session saved with NumPy 2.0+ but loading with NumPy <2.0, OR")
|
|
903
|
+
print(" - Session saved with NumPy <2.0 but loading with NumPy 2.0+")
|
|
904
|
+
print()
|
|
905
|
+
print("Solutions:")
|
|
906
|
+
if saved_numpy != 'unknown':
|
|
907
|
+
print(f" 1. Install matching version: pip install 'numpy=={saved_numpy}'")
|
|
908
|
+
else:
|
|
909
|
+
print(" 1. Try installing NumPy <2.0: pip install 'numpy<2.0'")
|
|
910
|
+
print(" OR try installing NumPy 2.0+: pip install 'numpy>=2.0'")
|
|
911
|
+
print(" 2. Recreate the session from original data files")
|
|
912
|
+
else:
|
|
913
|
+
print(f"\nERROR: Module import error when loading: {filename}")
|
|
914
|
+
print(f"Error: {e}")
|
|
915
|
+
print("This usually indicates a package version mismatch.")
|
|
916
|
+
return None
|
|
822
917
|
except Exception as e:
|
|
823
918
|
print(f"Failed to load session: {e}")
|
|
824
919
|
return None
|
|
@@ -1657,6 +1752,37 @@ def load_ec_session(filename: str):
|
|
|
1657
1752
|
try:
|
|
1658
1753
|
with open(filename, 'rb') as f:
|
|
1659
1754
|
sess = pickle.load(f)
|
|
1755
|
+
except ModuleNotFoundError as e:
|
|
1756
|
+
# Handle numpy._core and other module import errors
|
|
1757
|
+
if '_core' in str(e) or 'numpy' in str(e).lower():
|
|
1758
|
+
# Try to extract version info before the error
|
|
1759
|
+
saved_versions = _try_extract_version_from_pickle(filename)
|
|
1760
|
+
current_numpy = _get_current_numpy_version()
|
|
1761
|
+
|
|
1762
|
+
saved_numpy = saved_versions.get('numpy', 'unknown')
|
|
1763
|
+
|
|
1764
|
+
print(f"\nERROR: NumPy version mismatch detected when loading: {filename}")
|
|
1765
|
+
print("This session was saved with a different NumPy version.")
|
|
1766
|
+
print()
|
|
1767
|
+
print(f"Session was saved with: NumPy {saved_numpy}")
|
|
1768
|
+
print(f"Currently installed: NumPy {current_numpy}")
|
|
1769
|
+
print()
|
|
1770
|
+
print("The error 'No module named numpy._core' indicates:")
|
|
1771
|
+
print(" - Session saved with NumPy 2.0+ but loading with NumPy <2.0, OR")
|
|
1772
|
+
print(" - Session saved with NumPy <2.0 but loading with NumPy 2.0+")
|
|
1773
|
+
print()
|
|
1774
|
+
print("Solutions:")
|
|
1775
|
+
if saved_numpy != 'unknown':
|
|
1776
|
+
print(f" 1. Install matching version: pip install 'numpy=={saved_numpy}'")
|
|
1777
|
+
else:
|
|
1778
|
+
print(" 1. Try installing NumPy <2.0: pip install 'numpy<2.0'")
|
|
1779
|
+
print(" OR try installing NumPy 2.0+: pip install 'numpy>=2.0'")
|
|
1780
|
+
print(" 2. Recreate the session from original data files")
|
|
1781
|
+
else:
|
|
1782
|
+
print(f"\nERROR: Module import error when loading: {filename}")
|
|
1783
|
+
print(f"Error: {e}")
|
|
1784
|
+
print("This usually indicates a package version mismatch.")
|
|
1785
|
+
return None
|
|
1660
1786
|
except Exception as e:
|
|
1661
1787
|
print(f"Failed to load EC session: {e}")
|
|
1662
1788
|
return None
|
|
@@ -2526,6 +2652,37 @@ def load_cpc_session(filename: str):
|
|
|
2526
2652
|
try:
|
|
2527
2653
|
with open(filename, 'rb') as f:
|
|
2528
2654
|
sess = pickle.load(f)
|
|
2655
|
+
except ModuleNotFoundError as e:
|
|
2656
|
+
# Handle numpy._core and other module import errors
|
|
2657
|
+
if '_core' in str(e) or 'numpy' in str(e).lower():
|
|
2658
|
+
# Try to extract version info before the error
|
|
2659
|
+
saved_versions = _try_extract_version_from_pickle(filename)
|
|
2660
|
+
current_numpy = _get_current_numpy_version()
|
|
2661
|
+
|
|
2662
|
+
saved_numpy = saved_versions.get('numpy', 'unknown')
|
|
2663
|
+
|
|
2664
|
+
print(f"\nERROR: NumPy version mismatch detected when loading: {filename}")
|
|
2665
|
+
print("This session was saved with a different NumPy version.")
|
|
2666
|
+
print()
|
|
2667
|
+
print(f"Session was saved with: NumPy {saved_numpy}")
|
|
2668
|
+
print(f"Currently installed: NumPy {current_numpy}")
|
|
2669
|
+
print()
|
|
2670
|
+
print("The error 'No module named numpy._core' indicates:")
|
|
2671
|
+
print(" - Session saved with NumPy 2.0+ but loading with NumPy <2.0, OR")
|
|
2672
|
+
print(" - Session saved with NumPy <2.0 but loading with NumPy 2.0+")
|
|
2673
|
+
print()
|
|
2674
|
+
print("Solutions:")
|
|
2675
|
+
if saved_numpy != 'unknown':
|
|
2676
|
+
print(f" 1. Install matching version: pip install 'numpy=={saved_numpy}'")
|
|
2677
|
+
else:
|
|
2678
|
+
print(" 1. Try installing NumPy <2.0: pip install 'numpy<2.0'")
|
|
2679
|
+
print(" OR try installing NumPy 2.0+: pip install 'numpy>=2.0'")
|
|
2680
|
+
print(" 2. Recreate the session from original data files")
|
|
2681
|
+
else:
|
|
2682
|
+
print(f"\nERROR: Module import error when loading: {filename}")
|
|
2683
|
+
print(f"Error: {e}")
|
|
2684
|
+
print("This usually indicates a package version mismatch.")
|
|
2685
|
+
return None
|
|
2529
2686
|
except Exception as e:
|
|
2530
2687
|
print(f"Failed to load session: {e}")
|
|
2531
2688
|
return None
|
batplot/style.py
CHANGED
|
@@ -358,6 +358,13 @@ def print_style_info(
|
|
|
358
358
|
print(f"Font family chain (rcParams['font.sans-serif']): {plt.rcParams.get('font.sans-serif')}")
|
|
359
359
|
print(f"Mathtext fontset: {plt.rcParams.get('mathtext.fontset')}")
|
|
360
360
|
|
|
361
|
+
# Report whether data axes were swapped via --ro when this figure was created
|
|
362
|
+
try:
|
|
363
|
+
ro_active = bool(getattr(fig, "_ro_active", False))
|
|
364
|
+
except Exception:
|
|
365
|
+
ro_active = False
|
|
366
|
+
print(f"Data axes swapped via --ro: {'YES' if ro_active else 'no'}")
|
|
367
|
+
|
|
361
368
|
# Rotation angle
|
|
362
369
|
rotation_angle = getattr(ax, '_rotation_angle', 0)
|
|
363
370
|
if rotation_angle != 0:
|
|
@@ -646,6 +653,8 @@ def export_style_config(
|
|
|
646
653
|
}
|
|
647
654
|
# Save rotation angle
|
|
648
655
|
cfg["rotation_angle"] = getattr(ax, '_rotation_angle', 0)
|
|
656
|
+
# Track whether data axes were swapped via --ro when this style was saved
|
|
657
|
+
cfg["ro_active"] = bool(getattr(fig, '_ro_active', False))
|
|
649
658
|
|
|
650
659
|
# Save curve names visibility
|
|
651
660
|
cfg["curve_names_visible"] = True # Default to visible
|
|
@@ -853,6 +862,17 @@ def apply_style_config(
|
|
|
853
862
|
except Exception as e:
|
|
854
863
|
print(f"Could not read config: {e}")
|
|
855
864
|
return
|
|
865
|
+
# Enforce compatibility between style/geometry ro state and current figure ro state.
|
|
866
|
+
# Styles saved from a plot using --ro (swapped x/y) must not be applied to a non-ro plot, and vice versa.
|
|
867
|
+
file_ro = bool(cfg.get("ro_active", False))
|
|
868
|
+
current_ro = bool(getattr(fig, "_ro_active", False))
|
|
869
|
+
if file_ro != current_ro:
|
|
870
|
+
if file_ro:
|
|
871
|
+
print("Warning: Style/geometry file was saved with --ro (swapped x/y axes); current plot is not using --ro.")
|
|
872
|
+
else:
|
|
873
|
+
print("Warning: Style/geometry file was saved without --ro; current plot was created with --ro.")
|
|
874
|
+
print("Not applying style/geometry to avoid corrupting axis orientation.")
|
|
875
|
+
return
|
|
856
876
|
# Save current labelpad values BEFORE any style changes
|
|
857
877
|
saved_xlabelpad = None
|
|
858
878
|
saved_ylabelpad = None
|
|
@@ -878,8 +898,7 @@ def apply_style_config(
|
|
|
878
898
|
if not keep_canvas_fixed:
|
|
879
899
|
# Use forward=False to prevent automatic subplot adjustment that can shift the plot
|
|
880
900
|
fig.set_size_inches(fw, fh, forward=False)
|
|
881
|
-
|
|
882
|
-
print("(Canvas fixed) Ignoring style figure size request.")
|
|
901
|
+
# No message needed when canvas is fixed - this is normal behavior
|
|
883
902
|
except Exception as e:
|
|
884
903
|
print(f"Warning: could not parse figure size: {e}")
|
|
885
904
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: batplot
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.4
|
|
4
4
|
Summary: Interactive plotting tool for material science (1D plot) and electrochemistry (GC, CV, dQ/dV, CPC, operando) with batch processing
|
|
5
5
|
Author-email: Tian Dai <tianda@uio.no>
|
|
6
6
|
License: MIT License
|
|
@@ -31,20 +31,15 @@ Project-URL: Issues, https://github.com/TianDai1729/batplot/issues
|
|
|
31
31
|
Classifier: Programming Language :: Python
|
|
32
32
|
Classifier: Programming Language :: Python :: 3
|
|
33
33
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
34
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
35
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
36
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
39
34
|
Classifier: Programming Language :: Python :: 3.13
|
|
40
35
|
Classifier: Operating System :: OS Independent
|
|
41
36
|
Classifier: Intended Audience :: Science/Research
|
|
42
37
|
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
43
|
-
Requires-Python:
|
|
38
|
+
Requires-Python: <3.14,>=3.13
|
|
44
39
|
Description-Content-Type: text/markdown
|
|
45
40
|
License-File: LICENSE
|
|
46
|
-
Requires-Dist: numpy
|
|
47
|
-
Requires-Dist: matplotlib
|
|
41
|
+
Requires-Dist: numpy==2.3.4
|
|
42
|
+
Requires-Dist: matplotlib==3.10.7
|
|
48
43
|
Requires-Dist: rich>=10.0.0
|
|
49
44
|
Requires-Dist: openpyxl>=3.0.0
|
|
50
45
|
Dynamic: license-file
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
batplot/__init__.py,sha256=
|
|
1
|
+
batplot/__init__.py,sha256=A2c5RdJQEC4-Snfk1leoeq38dCyGoAxBuEvXo3Kb50A,118
|
|
2
2
|
batplot/args.py,sha256=mrDjMURp_OQnXrAwl9WnE_FW4HJu940O7NmL9QWQnD4,35189
|
|
3
|
-
batplot/batch.py,sha256=
|
|
4
|
-
batplot/batplot.py,sha256=
|
|
3
|
+
batplot/batch.py,sha256=P2HNBYqrSzDr2fruYz8BFKwNFX6vdJpHDZpaG_hmEc8,55253
|
|
4
|
+
batplot/batplot.py,sha256=IDnc-aGIMo1oMfXsQjU_koKqq2E9xXNn1Bm92xsmeYE,177109
|
|
5
5
|
batplot/cif.py,sha256=JfHwNf3SHrcpALc_F5NjJmQ3lg71MBRSaIUJjGYPTx8,30120
|
|
6
|
-
batplot/cli.py,sha256=
|
|
6
|
+
batplot/cli.py,sha256=2-7NtxBlyOUfzHVwP7vZK7OZlyKyPVy-3daKN-MPWyU,6657
|
|
7
7
|
batplot/color_utils.py,sha256=7InQLVo1XTg7sgAbltM2KeDSFJgr787YEaV9vJbIoWY,20460
|
|
8
8
|
batplot/config.py,sha256=6nGY7fKN4T5KZUGQS2ArUBgEkLAL0j37XwG5SCVQgKA,6420
|
|
9
9
|
batplot/converters.py,sha256=rR2WMPM0nR5E3eZI3gWbaJf_AfbdQx3urVSbJmZXNzo,8237
|
|
10
|
-
batplot/cpc_interactive.py,sha256=
|
|
11
|
-
batplot/electrochem_interactive.py,sha256=
|
|
12
|
-
batplot/interactive.py,sha256=
|
|
10
|
+
batplot/cpc_interactive.py,sha256=qadWV2PaQMsqM16mSp5r1-WP7di0JCnzNI4RJy27alo,239616
|
|
11
|
+
batplot/electrochem_interactive.py,sha256=8mFr5vtWb_ZDiJmZWXjkARq21D2GflT8-J2xvCGvDIc,222769
|
|
12
|
+
batplot/interactive.py,sha256=5u2ulhTzRr7fA-INc5hfcz7xaHObnnigiXjrgdtE7XE,206728
|
|
13
13
|
batplot/manual.py,sha256=pbRI6G4Pm12pOW8LrOLWWu7IEOtqWN3tRHtgge50LlA,11556
|
|
14
14
|
batplot/modes.py,sha256=qE2OsOQQKhwOWene5zxJeuuewTrZxubtahQuz5je7ok,37252
|
|
15
15
|
batplot/operando.py,sha256=p2Ug1mFUQxaU702cTBGgJKb3_v1C2p3LLUwfXaVBpPY,28311
|
|
16
16
|
batplot/operando_ec_interactive.py,sha256=8GQ47-I8SLTS88sFEk8m3vDxFEjSfD3hao62Qke7SxA,305137
|
|
17
17
|
batplot/plotting.py,sha256=hG2_EdDhF1Qpn1XfZKdCQ5-w_m9gUYFbr804UQ5QjsU,10841
|
|
18
18
|
batplot/readers.py,sha256=kAI0AvYrdfGRZkvADJ4riN96IWtrH24aAoZpBtONTbw,112960
|
|
19
|
-
batplot/session.py,sha256=
|
|
20
|
-
batplot/style.py,sha256=
|
|
19
|
+
batplot/session.py,sha256=sYhY3WH-Kks5SMO91kfGDkSs32SX_iFS62ZkUm3jwzY,142722
|
|
20
|
+
batplot/style.py,sha256=jXtFaJR1aa6vIHupmDNqY2NY5Rgtw49UxF7cS4y8fCA,63375
|
|
21
21
|
batplot/ui.py,sha256=ifpbK74juUzLMCt-sJGVaWtpDb1NMRJzs2YyiwwafzY,35302
|
|
22
22
|
batplot/utils.py,sha256=LY2-Axr3DsQMTxuXe48vSjrLJKEnkzkZjdSFdQizbpg,37599
|
|
23
23
|
batplot/version_check.py,sha256=--U_74DKgHbGtVdBsg9DfUJ10S5-OMXT-rzaYjK0JBc,9997
|
|
24
24
|
batplot/data/USER_MANUAL.md,sha256=VYPvNZt3Fy8Z4Izr2FnQBw9vEaFTPkybhHDnF-OuKws,17694
|
|
25
|
-
batplot-1.8.
|
|
25
|
+
batplot-1.8.4.dist-info/licenses/LICENSE,sha256=2PAnHeCiTfgI7aKZLWr0G56HI9fGKQ0CEbQ02H-yExQ,1065
|
|
26
26
|
batplot_backup_20251121_223043/__init__.py,sha256=3s2DUQuTbWs65hoN9cQQ8IiJbaFJY8fNxiCpwRBYoOA,118
|
|
27
27
|
batplot_backup_20251121_223043/args.py,sha256=OH-h84QhN-IhMS8sPAsSEqccHD3wpeMgmXa_fqv5xtg,21215
|
|
28
28
|
batplot_backup_20251121_223043/batch.py,sha256=oI7PONJyciHDOqNPq-8fnOQMyn9CpAdVznKaEdsy0ig,48650
|
|
@@ -68,8 +68,8 @@ batplot_backup_20251221_101150/style.py,sha256=ig1ozX4dhEsXf5JKaPZOvgVS3CWx-BTFS
|
|
|
68
68
|
batplot_backup_20251221_101150/ui.py,sha256=ifpbK74juUzLMCt-sJGVaWtpDb1NMRJzs2YyiwwafzY,35302
|
|
69
69
|
batplot_backup_20251221_101150/utils.py,sha256=LY2-Axr3DsQMTxuXe48vSjrLJKEnkzkZjdSFdQizbpg,37599
|
|
70
70
|
batplot_backup_20251221_101150/version_check.py,sha256=ztTHwqgWd8OlS9PLLY5A_TabWxBASDA_-5yyN15PZC8,9996
|
|
71
|
-
batplot-1.8.
|
|
72
|
-
batplot-1.8.
|
|
73
|
-
batplot-1.8.
|
|
74
|
-
batplot-1.8.
|
|
75
|
-
batplot-1.8.
|
|
71
|
+
batplot-1.8.4.dist-info/METADATA,sha256=da51wuDhz7bqCTInJ_tjZzZy2wC6ujSfsrgYIi-GYgI,7175
|
|
72
|
+
batplot-1.8.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
73
|
+
batplot-1.8.4.dist-info/entry_points.txt,sha256=73GgH3Zs-qGIvgiyQLgGsSW-ryOwPPKHveOW6TDIR5Q,82
|
|
74
|
+
batplot-1.8.4.dist-info/top_level.txt,sha256=Z5Q4sAiT_FDqZqhlLsYn9avRTuFAEEf3AVfkswxOb18,70
|
|
75
|
+
batplot-1.8.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|