batplot 1.8.1__py3-none-any.whl → 1.8.3__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.
Potentially problematic release.
This version of batplot might be problematic. Click here for more details.
- batplot/__init__.py +1 -1
- batplot/args.py +2 -0
- batplot/batch.py +23 -0
- batplot/batplot.py +101 -12
- batplot/cpc_interactive.py +25 -3
- batplot/electrochem_interactive.py +20 -4
- batplot/interactive.py +19 -15
- batplot/modes.py +12 -12
- batplot/operando_ec_interactive.py +4 -4
- batplot/session.py +218 -0
- batplot/style.py +21 -2
- batplot/version_check.py +1 -1
- {batplot-1.8.1.dist-info → batplot-1.8.3.dist-info}/METADATA +1 -1
- batplot-1.8.3.dist-info/RECORD +75 -0
- {batplot-1.8.1.dist-info → batplot-1.8.3.dist-info}/top_level.txt +1 -0
- batplot_backup_20251221_101150/__init__.py +5 -0
- batplot_backup_20251221_101150/args.py +625 -0
- batplot_backup_20251221_101150/batch.py +1176 -0
- batplot_backup_20251221_101150/batplot.py +3589 -0
- batplot_backup_20251221_101150/cif.py +823 -0
- batplot_backup_20251221_101150/cli.py +149 -0
- batplot_backup_20251221_101150/color_utils.py +547 -0
- batplot_backup_20251221_101150/config.py +198 -0
- batplot_backup_20251221_101150/converters.py +204 -0
- batplot_backup_20251221_101150/cpc_interactive.py +4409 -0
- batplot_backup_20251221_101150/electrochem_interactive.py +4520 -0
- batplot_backup_20251221_101150/interactive.py +3894 -0
- batplot_backup_20251221_101150/manual.py +323 -0
- batplot_backup_20251221_101150/modes.py +799 -0
- batplot_backup_20251221_101150/operando.py +603 -0
- batplot_backup_20251221_101150/operando_ec_interactive.py +5487 -0
- batplot_backup_20251221_101150/plotting.py +228 -0
- batplot_backup_20251221_101150/readers.py +2607 -0
- batplot_backup_20251221_101150/session.py +2951 -0
- batplot_backup_20251221_101150/style.py +1441 -0
- batplot_backup_20251221_101150/ui.py +790 -0
- batplot_backup_20251221_101150/utils.py +1046 -0
- batplot_backup_20251221_101150/version_check.py +253 -0
- batplot-1.8.1.dist-info/RECORD +0 -52
- {batplot-1.8.1.dist-info → batplot-1.8.3.dist-info}/WHEEL +0 -0
- {batplot-1.8.1.dist-info → batplot-1.8.3.dist-info}/entry_points.txt +0 -0
- {batplot-1.8.1.dist-info → batplot-1.8.3.dist-info}/licenses/LICENSE +0 -0
batplot/__init__.py
CHANGED
batplot/args.py
CHANGED
|
@@ -152,6 +152,8 @@ def _print_general_help() -> None:
|
|
|
152
152
|
" batplot --all style.bps # Batch with style: apply style.bps to all files\n"
|
|
153
153
|
" batplot --all ./Style/style.bps # Batch with style: use relative path to style file\n"
|
|
154
154
|
" batplot --all config.bpsg # Batch with style+geom: apply to all XY files\n"
|
|
155
|
+
" batplot file1.xy:1.54 file2.qye --stack # Stack mode: stack all files vertically\n"
|
|
156
|
+
" batplot file1.xy:1.54 file2.qye structure.cif --stack --i # Stack mode: stack all files vertically with cif ticks\n"
|
|
155
157
|
" batplot file1.qye file2.qye style.bps # Apply style to multiple files and export\n"
|
|
156
158
|
" batplot file1.xy file2.xye ./Style/style.bps # Apply style from relative path\n\n"
|
|
157
159
|
" [Electrochemistry]\n"
|
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,9 +1790,40 @@ 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
|
+
print(f"\nERROR: NumPy version mismatch detected when loading: {sess_path}")
|
|
1797
|
+
print("This session was saved with a different NumPy version.")
|
|
1798
|
+
print("The error 'No module named numpy._core' indicates:")
|
|
1799
|
+
print(" - Session saved with NumPy 2.0+ but loading with NumPy <2.0, OR")
|
|
1800
|
+
print(" - Session saved with NumPy <2.0 but loading with NumPy 2.0+")
|
|
1801
|
+
print("\nSolutions:")
|
|
1802
|
+
print(" 1. Check NumPy version: python3 -c 'import numpy; print(numpy.__version__)'")
|
|
1803
|
+
print(" 2. Install matching version:")
|
|
1804
|
+
print(" - If session was saved with NumPy 2.0+: pip install 'numpy>=2.0'")
|
|
1805
|
+
print(" - If session was saved with NumPy <2.0: pip install 'numpy<2.0'")
|
|
1806
|
+
print(" 3. Recreate the session from original data files")
|
|
1807
|
+
else:
|
|
1808
|
+
print(f"\nERROR: Module import error when loading: {sess_path}")
|
|
1809
|
+
print(f"Error: {e}")
|
|
1810
|
+
print("This usually indicates a package version mismatch.")
|
|
1811
|
+
print("Try installing matching package versions or recreate the session.")
|
|
1812
|
+
exit(1)
|
|
1788
1813
|
except Exception as e:
|
|
1789
1814
|
print(f"Failed to load session: {e}")
|
|
1790
1815
|
exit(1)
|
|
1816
|
+
|
|
1817
|
+
# Check package version compatibility (if version info is available)
|
|
1818
|
+
try:
|
|
1819
|
+
from .session import _get_package_versions, _check_package_compatibility
|
|
1820
|
+
saved_versions = sess.get('package_versions', {})
|
|
1821
|
+
current_versions = _get_package_versions()
|
|
1822
|
+
if saved_versions and not _check_package_compatibility(saved_versions, current_versions, sess_path):
|
|
1823
|
+
exit(1)
|
|
1824
|
+
except Exception:
|
|
1825
|
+
# If compatibility checking fails, continue anyway (backward compatibility)
|
|
1826
|
+
pass
|
|
1791
1827
|
# If it's an EC GC session, load and open EC interactive menu directly
|
|
1792
1828
|
if isinstance(sess, dict) and sess.get('kind') == 'ec_gc':
|
|
1793
1829
|
try:
|
|
@@ -1882,6 +1918,11 @@ def batplot_main() -> int:
|
|
|
1882
1918
|
# Reconstruct minimal state and go to interactive if requested
|
|
1883
1919
|
plt.ion() if args.interactive else None
|
|
1884
1920
|
fig, ax = plt.subplots(figsize=(8,6))
|
|
1921
|
+
# Restore ro flag from session (if present) so style/geom imports can enforce compatibility
|
|
1922
|
+
try:
|
|
1923
|
+
fig._ro_active = bool(sess.get('ro_active', False))
|
|
1924
|
+
except Exception:
|
|
1925
|
+
pass
|
|
1885
1926
|
y_data_list = []
|
|
1886
1927
|
x_data_list = []
|
|
1887
1928
|
labels_list = []
|
|
@@ -1890,10 +1931,31 @@ def batplot_main() -> int:
|
|
|
1890
1931
|
x_full_list = []
|
|
1891
1932
|
raw_y_full_list = []
|
|
1892
1933
|
offsets_list = []
|
|
1893
|
-
tick_state
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1934
|
+
# Load tick_state from wasd_state if available (version 2+), otherwise use defaults
|
|
1935
|
+
wasd_loaded = sess.get('wasd_state')
|
|
1936
|
+
if wasd_loaded and isinstance(wasd_loaded, dict):
|
|
1937
|
+
# Convert wasd_state to tick_state format
|
|
1938
|
+
tick_state = {}
|
|
1939
|
+
for side_key, prefix in [('top', 't'), ('bottom', 'b'), ('left', 'l'), ('right', 'r')]:
|
|
1940
|
+
s = wasd_loaded.get(side_key, {})
|
|
1941
|
+
tick_state[f'{prefix}_ticks'] = bool(s.get('ticks', side_key in ('bottom', 'left')))
|
|
1942
|
+
tick_state[f'{prefix}_labels'] = bool(s.get('labels', side_key in ('bottom', 'left')))
|
|
1943
|
+
tick_state[f'm{prefix}x' if prefix in 'tb' else f'm{prefix}y'] = bool(s.get('minor', False))
|
|
1944
|
+
# Legacy keys for backward compatibility
|
|
1945
|
+
tick_state['bx'] = tick_state.get('b_ticks', True)
|
|
1946
|
+
tick_state['tx'] = tick_state.get('t_ticks', False)
|
|
1947
|
+
tick_state['ly'] = tick_state.get('l_ticks', True)
|
|
1948
|
+
tick_state['ry'] = tick_state.get('r_ticks', False)
|
|
1949
|
+
tick_state['mbx'] = tick_state.get('mbx', False)
|
|
1950
|
+
tick_state['mtx'] = tick_state.get('mtx', False)
|
|
1951
|
+
tick_state['mly'] = tick_state.get('mly', False)
|
|
1952
|
+
tick_state['mry'] = tick_state.get('mry', False)
|
|
1953
|
+
else:
|
|
1954
|
+
# Fallback to legacy tick_state or defaults
|
|
1955
|
+
tick_state = sess.get('tick_state', {
|
|
1956
|
+
'bx': True,'tx': False,'ly': True,'ry': False,
|
|
1957
|
+
'mbx': False,'mtx': False,'mly': False,'mry': False
|
|
1958
|
+
})
|
|
1897
1959
|
saved_stack = bool(sess.get('args_subset', {}).get('stack', False))
|
|
1898
1960
|
# Pull data
|
|
1899
1961
|
# --- Robust reconstruction of stored curves ---
|
|
@@ -1954,8 +2016,27 @@ def batplot_main() -> int:
|
|
|
1954
2016
|
pass
|
|
1955
2017
|
labels_list[:] = sess.get('labels', [f"Curve {i+1}" for i in range(len(y_data_list))])
|
|
1956
2018
|
delta = sess.get('delta', 0.0)
|
|
2019
|
+
# Apply tick state (labels visibility) BEFORE setting axis labels
|
|
2020
|
+
try:
|
|
2021
|
+
ax.tick_params(axis='x',
|
|
2022
|
+
bottom=tick_state.get('b_ticks', tick_state.get('bx', True)),
|
|
2023
|
+
labelbottom=tick_state.get('b_labels', tick_state.get('bx', True)),
|
|
2024
|
+
top=tick_state.get('t_ticks', tick_state.get('tx', False)),
|
|
2025
|
+
labeltop=tick_state.get('t_labels', tick_state.get('tx', False)))
|
|
2026
|
+
ax.tick_params(axis='y',
|
|
2027
|
+
left=tick_state.get('l_ticks', tick_state.get('ly', True)),
|
|
2028
|
+
labelleft=tick_state.get('l_labels', tick_state.get('ly', True)),
|
|
2029
|
+
right=tick_state.get('r_ticks', tick_state.get('ry', False)),
|
|
2030
|
+
labelright=tick_state.get('r_labels', tick_state.get('ry', False)))
|
|
2031
|
+
except Exception:
|
|
2032
|
+
pass
|
|
1957
2033
|
ax.set_xlabel(sess.get('axis', {}).get('xlabel', 'X'))
|
|
1958
2034
|
ax.set_ylabel(sess.get('axis', {}).get('ylabel', 'Intensity'))
|
|
2035
|
+
# Store tick_state on axes for interactive menu
|
|
2036
|
+
try:
|
|
2037
|
+
ax._saved_tick_state = dict(tick_state)
|
|
2038
|
+
except Exception:
|
|
2039
|
+
pass
|
|
1959
2040
|
|
|
1960
2041
|
# Restore normalization ranges (if saved)
|
|
1961
2042
|
axis_cfg = sess.get('axis', {})
|
|
@@ -2933,19 +3014,21 @@ def batplot_main() -> int:
|
|
|
2933
3014
|
offset += increment
|
|
2934
3015
|
|
|
2935
3016
|
# ---- Plot curve ----
|
|
2936
|
-
# Swap x and y if --ro flag is set
|
|
3017
|
+
# Swap x and y if --ro flag is set (and keep lists aligned once)
|
|
2937
3018
|
if getattr(args, 'ro', False):
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
x_data_list.append(y_plot_offset)
|
|
3019
|
+
x_plotted = y_plot_offset # goes on x-axis when rotated
|
|
3020
|
+
y_plotted = x_plot # goes on y-axis when rotated
|
|
2941
3021
|
else:
|
|
3022
|
+
x_plotted = x_plot
|
|
3023
|
+
y_plotted = y_plot_offset
|
|
2942
3024
|
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
3025
|
+
ax.plot(x_plotted, y_plotted, "-", lw=1, alpha=0.8)
|
|
3026
|
+
x_data_list.append(x_plotted)
|
|
3027
|
+
y_data_list.append(y_plotted.copy())
|
|
2946
3028
|
labels_list.append(label)
|
|
2947
3029
|
# Store current normalized (subset) (used by rearrange logic)
|
|
2948
|
-
orig_y.
|
|
3030
|
+
# Keep orig_y aligned with the plotted y data to avoid length mismatch on undo/relabel.
|
|
3031
|
+
orig_y.append(y_plotted.copy())
|
|
2949
3032
|
|
|
2950
3033
|
# ---------------- Force axis to fit all data before labels ----------------
|
|
2951
3034
|
ax.relim()
|
|
@@ -3458,6 +3541,12 @@ def batplot_main() -> int:
|
|
|
3458
3541
|
fig.set_tight_layout(False)
|
|
3459
3542
|
except Exception:
|
|
3460
3543
|
pass
|
|
3544
|
+
|
|
3545
|
+
# Track whether data axes were swapped via --ro for this figure
|
|
3546
|
+
try:
|
|
3547
|
+
fig._ro_active = bool(getattr(args, "ro", False))
|
|
3548
|
+
except Exception:
|
|
3549
|
+
pass
|
|
3461
3550
|
|
|
3462
3551
|
# Build CIF globals dict for explicit passing
|
|
3463
3552
|
cif_globals = {
|
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
|
|
|
@@ -3458,6 +3470,11 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
3458
3470
|
if not cmd:
|
|
3459
3471
|
continue
|
|
3460
3472
|
if cmd == 'q':
|
|
3473
|
+
# Update ax._saved_tick_state before exiting so changes are persisted
|
|
3474
|
+
try:
|
|
3475
|
+
ax._saved_tick_state = dict(tick_state)
|
|
3476
|
+
except Exception:
|
|
3477
|
+
pass
|
|
3461
3478
|
break
|
|
3462
3479
|
if cmd == 'i':
|
|
3463
3480
|
# Invert tick direction (toggle between 'out' and 'in')
|
|
@@ -3779,6 +3796,11 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
3779
3796
|
tick_state['mry'] = bool(wasd['right']['minor'])
|
|
3780
3797
|
if changed:
|
|
3781
3798
|
push_state("wasd-toggle")
|
|
3799
|
+
# Update ax._saved_tick_state so dump_session can read it
|
|
3800
|
+
try:
|
|
3801
|
+
ax._saved_tick_state = dict(tick_state)
|
|
3802
|
+
except Exception:
|
|
3803
|
+
pass
|
|
3782
3804
|
_apply_wasd(changed_sides if changed_sides else None)
|
|
3783
3805
|
# Single draw at the end after all positioning is complete
|
|
3784
3806
|
try:
|
|
@@ -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)
|
|
@@ -3313,6 +3307,11 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
3313
3307
|
if not cmd:
|
|
3314
3308
|
continue
|
|
3315
3309
|
if cmd == 'q':
|
|
3310
|
+
# Update ax._saved_tick_state before exiting so changes are persisted
|
|
3311
|
+
try:
|
|
3312
|
+
ax._saved_tick_state = dict(tick_state)
|
|
3313
|
+
except Exception:
|
|
3314
|
+
pass
|
|
3316
3315
|
break
|
|
3317
3316
|
if cmd == 'i':
|
|
3318
3317
|
# Invert tick direction (toggle between 'out' and 'in')
|
|
@@ -3584,6 +3583,11 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
3584
3583
|
continue
|
|
3585
3584
|
# Unknown code
|
|
3586
3585
|
print(f"Unknown code: {p}")
|
|
3586
|
+
# After tick toggles, update ax._saved_tick_state so dump_session can read it
|
|
3587
|
+
try:
|
|
3588
|
+
ax._saved_tick_state = dict(tick_state)
|
|
3589
|
+
except Exception:
|
|
3590
|
+
pass
|
|
3587
3591
|
# After tick toggles, update visibility and reposition ALL axis labels for independence
|
|
3588
3592
|
update_tick_visibility()
|
|
3589
3593
|
update_labels(ax, y_data_list, label_text_objects, args.stack, getattr(fig, '_stack_label_at_bottom', False))
|
batplot/modes.py
CHANGED
|
@@ -280,8 +280,8 @@ def handle_cv_mode(args) -> int:
|
|
|
280
280
|
ax.set_xlabel('Current (mA)', labelpad=8.0)
|
|
281
281
|
ax.set_ylabel('Voltage (V)', labelpad=8.0)
|
|
282
282
|
else:
|
|
283
|
-
|
|
284
|
-
|
|
283
|
+
ax.set_xlabel('Voltage (V)', labelpad=8.0)
|
|
284
|
+
ax.set_ylabel('Current (mA)', labelpad=8.0)
|
|
285
285
|
legend = ax.legend(title='Cycle')
|
|
286
286
|
legend.get_title().set_fontsize('medium')
|
|
287
287
|
# Adjust margins to prevent label clipping
|
|
@@ -642,8 +642,8 @@ def handle_gc_mode(args) -> int:
|
|
|
642
642
|
ln_c, = ax.plot(y_b, x_b, '-', color=base_colors[(cyc-1) % len(base_colors)],
|
|
643
643
|
linewidth=2.0, label=str(cyc), alpha=0.8)
|
|
644
644
|
else:
|
|
645
|
-
|
|
646
|
-
|
|
645
|
+
ln_c, = ax.plot(x_b, y_b, '-', color=base_colors[(cyc-1) % len(base_colors)],
|
|
646
|
+
linewidth=2.0, label=str(cyc), alpha=0.8)
|
|
647
647
|
else:
|
|
648
648
|
ln_c = None
|
|
649
649
|
mask_d = (cyc_int == cyc) & discharge_mask
|
|
@@ -656,8 +656,8 @@ def handle_gc_mode(args) -> int:
|
|
|
656
656
|
ln_d, = ax.plot(yd_b, xd_b, '-', color=base_colors[(cyc-1) % len(base_colors)],
|
|
657
657
|
linewidth=2.0, label=lbl, alpha=0.8)
|
|
658
658
|
else:
|
|
659
|
-
|
|
660
|
-
|
|
659
|
+
ln_d, = ax.plot(xd_b, yd_b, '-', color=base_colors[(cyc-1) % len(base_colors)],
|
|
660
|
+
linewidth=2.0, label=lbl, alpha=0.8)
|
|
661
661
|
else:
|
|
662
662
|
ln_d = None
|
|
663
663
|
cycle_lines[cyc] = {"charge": ln_c, "discharge": ln_d}
|
|
@@ -677,8 +677,8 @@ def handle_gc_mode(args) -> int:
|
|
|
677
677
|
ln_c, = ax.plot(y_b, x_b, '-', color=base_colors[(cyc-1) % len(base_colors)],
|
|
678
678
|
linewidth=2.0, label=str(cyc), alpha=0.8)
|
|
679
679
|
else:
|
|
680
|
-
|
|
681
|
-
|
|
680
|
+
ln_c, = ax.plot(x_b, y_b, '-', color=base_colors[(cyc-1) % len(base_colors)],
|
|
681
|
+
linewidth=2.0, label=str(cyc), alpha=0.8)
|
|
682
682
|
ln_d = None
|
|
683
683
|
if i < len(dch_blocks):
|
|
684
684
|
a, b = dch_blocks[i]
|
|
@@ -690,8 +690,8 @@ def handle_gc_mode(args) -> int:
|
|
|
690
690
|
ln_d, = ax.plot(yd_b, xd_b, '-', color=base_colors[(cyc-1) % len(base_colors)],
|
|
691
691
|
linewidth=2.0, label=lbl, alpha=0.8)
|
|
692
692
|
else:
|
|
693
|
-
|
|
694
|
-
|
|
693
|
+
ln_d, = ax.plot(xd_b, yd_b, '-', color=base_colors[(cyc-1) % len(base_colors)],
|
|
694
|
+
linewidth=2.0, label=lbl, alpha=0.8)
|
|
695
695
|
cycle_lines[cyc] = {"charge": ln_c, "discharge": ln_d}
|
|
696
696
|
|
|
697
697
|
# Swap x and y if --ro flag is set
|
|
@@ -699,8 +699,8 @@ def handle_gc_mode(args) -> int:
|
|
|
699
699
|
ax.set_xlabel('Voltage (V)', labelpad=8.0)
|
|
700
700
|
ax.set_ylabel(x_label_gc, labelpad=8.0)
|
|
701
701
|
else:
|
|
702
|
-
|
|
703
|
-
|
|
702
|
+
ax.set_xlabel(x_label_gc, labelpad=8.0)
|
|
703
|
+
ax.set_ylabel('Voltage (V)', labelpad=8.0)
|
|
704
704
|
legend = ax.legend(title='Cycle')
|
|
705
705
|
legend.get_title().set_fontsize('medium')
|
|
706
706
|
fig.subplots_adjust(left=0.12, right=0.95, top=0.88, bottom=0.15)
|
|
@@ -354,7 +354,7 @@ def _draw_custom_colorbar(cbar_ax, im, label='Intensity', label_mode='normal'):
|
|
|
354
354
|
|
|
355
355
|
def _update_custom_colorbar(cbar_ax, im=None, label=None, label_mode=None):
|
|
356
356
|
"""Update the custom colorbar when colormap or limits change.
|
|
357
|
-
|
|
357
|
+
|
|
358
358
|
Args:
|
|
359
359
|
cbar_ax: Axes object containing the colorbar
|
|
360
360
|
im: Optional AxesImage object (if None, uses stored reference)
|
|
@@ -365,13 +365,13 @@ def _update_custom_colorbar(cbar_ax, im=None, label=None, label_mode=None):
|
|
|
365
365
|
im = getattr(cbar_ax, '_colorbar_im', None)
|
|
366
366
|
if im is None:
|
|
367
367
|
return
|
|
368
|
-
|
|
368
|
+
|
|
369
369
|
if label is None:
|
|
370
370
|
label = getattr(cbar_ax, '_colorbar_label', 'Intensity')
|
|
371
|
-
|
|
371
|
+
|
|
372
372
|
if label_mode is None:
|
|
373
373
|
label_mode = getattr(cbar_ax, '_colorbar_label_mode', 'normal')
|
|
374
|
-
|
|
374
|
+
|
|
375
375
|
# Redraw the colorbar
|
|
376
376
|
_draw_custom_colorbar(cbar_ax, im, label, label_mode)
|
|
377
377
|
|