batplot 1.7.28__py3-none-any.whl → 1.8.0__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/cpc_interactive.py +3 -0
- batplot/electrochem_interactive.py +90 -55
- batplot/interactive.py +92 -61
- batplot/modes.py +12 -12
- batplot/operando_ec_interactive.py +144 -74
- {batplot-1.7.28.dist-info → batplot-1.8.0.dist-info}/METADATA +1 -1
- {batplot-1.7.28.dist-info → batplot-1.8.0.dist-info}/RECORD +12 -12
- {batplot-1.7.28.dist-info → batplot-1.8.0.dist-info}/WHEEL +0 -0
- {batplot-1.7.28.dist-info → batplot-1.8.0.dist-info}/entry_points.txt +0 -0
- {batplot-1.7.28.dist-info → batplot-1.8.0.dist-info}/licenses/LICENSE +0 -0
- {batplot-1.7.28.dist-info → batplot-1.8.0.dist-info}/top_level.txt +0 -0
batplot/__init__.py
CHANGED
batplot/cpc_interactive.py
CHANGED
|
@@ -3141,6 +3141,9 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
3141
3141
|
_print_menu(); continue
|
|
3142
3142
|
elif key == 't':
|
|
3143
3143
|
# Unified WASD toggles for spines/ticks/minor/labels/title per side
|
|
3144
|
+
# Import UI positioning functions locally to ensure they're accessible in nested functions
|
|
3145
|
+
from .ui import position_top_xlabel as _ui_position_top_xlabel, position_bottom_xlabel as _ui_position_bottom_xlabel, position_left_ylabel as _ui_position_left_ylabel, position_right_ylabel as _ui_position_right_ylabel
|
|
3146
|
+
|
|
3144
3147
|
try:
|
|
3145
3148
|
# Local WASD state stored on figure to persist across openings
|
|
3146
3149
|
wasd = getattr(fig, '_cpc_wasd_state', None)
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
from typing import Dict, Iterable, List, Optional, Tuple
|
|
10
10
|
import json
|
|
11
11
|
import os
|
|
12
|
+
import sys
|
|
12
13
|
|
|
13
14
|
import matplotlib.pyplot as plt
|
|
14
15
|
import matplotlib.cm as cm
|
|
@@ -42,6 +43,37 @@ from .color_utils import (
|
|
|
42
43
|
)
|
|
43
44
|
|
|
44
45
|
|
|
46
|
+
class _FilterIMKWarning:
|
|
47
|
+
"""Filter that suppresses macOS IMKCFRunLoopWakeUpReliable warnings while preserving other errors."""
|
|
48
|
+
def __init__(self, original_stderr):
|
|
49
|
+
self.original_stderr = original_stderr
|
|
50
|
+
|
|
51
|
+
def write(self, message):
|
|
52
|
+
# Filter out the harmless macOS IMK warning
|
|
53
|
+
if 'IMKCFRunLoopWakeUpReliable' not in message:
|
|
54
|
+
self.original_stderr.write(message)
|
|
55
|
+
|
|
56
|
+
def flush(self):
|
|
57
|
+
self.original_stderr.flush()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _safe_input(prompt: str = "") -> str:
|
|
61
|
+
"""Wrapper around input() that suppresses macOS IMKCFRunLoopWakeUpReliable warnings.
|
|
62
|
+
|
|
63
|
+
This is a harmless macOS system message that appears when using input() in terminals.
|
|
64
|
+
"""
|
|
65
|
+
# Filter stderr to hide macOS IMK warnings while preserving other errors
|
|
66
|
+
original_stderr = sys.stderr
|
|
67
|
+
sys.stderr = _FilterIMKWarning(original_stderr)
|
|
68
|
+
try:
|
|
69
|
+
result = input(prompt)
|
|
70
|
+
return result
|
|
71
|
+
except (KeyboardInterrupt, EOFError):
|
|
72
|
+
raise
|
|
73
|
+
finally:
|
|
74
|
+
sys.stderr = original_stderr
|
|
75
|
+
|
|
76
|
+
|
|
45
77
|
def _colorize_menu(text):
|
|
46
78
|
"""Colorize menu items: command in cyan, colon in white, description in default."""
|
|
47
79
|
if ':' not in text:
|
|
@@ -937,6 +969,9 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
937
969
|
|
|
938
970
|
def _title_offset_menu():
|
|
939
971
|
"""Allow nudging duplicate top/right titles by single-pixel increments."""
|
|
972
|
+
# Import UI positioning functions locally to ensure they're accessible in nested functions
|
|
973
|
+
from .ui import position_top_xlabel as _ui_position_top_xlabel, position_bottom_xlabel as _ui_position_bottom_xlabel, position_left_ylabel as _ui_position_left_ylabel, position_right_ylabel as _ui_position_right_ylabel
|
|
974
|
+
|
|
940
975
|
def _dpi():
|
|
941
976
|
try:
|
|
942
977
|
return float(fig.dpi)
|
|
@@ -980,7 +1015,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
980
1015
|
current_y_px = _px_value('_top_xlabel_manual_offset_y_pts')
|
|
981
1016
|
current_x_px = _px_value('_top_xlabel_manual_offset_x_pts')
|
|
982
1017
|
print(f"Top title offset: Y={current_y_px:+.2f} px (positive=up), X={current_x_px:+.2f} px (positive=right)")
|
|
983
|
-
sub =
|
|
1018
|
+
sub = _safe_input(_colorize_prompt("top (w=up, s=down, a=left, d=right, 0=reset, q=back): ")).strip().lower()
|
|
984
1019
|
if not sub:
|
|
985
1020
|
continue
|
|
986
1021
|
if sub == 'q':
|
|
@@ -1018,7 +1053,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1018
1053
|
current_x_px = _px_value('_right_ylabel_manual_offset_x_pts')
|
|
1019
1054
|
current_y_px = _px_value('_right_ylabel_manual_offset_y_pts')
|
|
1020
1055
|
print(f"Right title offset: X={current_x_px:+.2f} px (positive=right), Y={current_y_px:+.2f} px (positive=up)")
|
|
1021
|
-
sub =
|
|
1056
|
+
sub = _safe_input(_colorize_prompt("right (d=right, a=left, w=up, s=down, 0=reset, q=back): ")).strip().lower()
|
|
1022
1057
|
if not sub:
|
|
1023
1058
|
continue
|
|
1024
1059
|
if sub == 'q':
|
|
@@ -1055,7 +1090,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1055
1090
|
while True:
|
|
1056
1091
|
current_y_px = _px_value('_bottom_xlabel_manual_offset_y_pts')
|
|
1057
1092
|
print(f"Bottom title offset: Y={current_y_px:+.2f} px (positive=down)")
|
|
1058
|
-
sub =
|
|
1093
|
+
sub = _safe_input(_colorize_prompt("bottom (s=down, w=up, 0=reset, q=back): ")).strip().lower()
|
|
1059
1094
|
if not sub:
|
|
1060
1095
|
continue
|
|
1061
1096
|
if sub == 'q':
|
|
@@ -1085,7 +1120,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1085
1120
|
while True:
|
|
1086
1121
|
current_x_px = _px_value('_left_ylabel_manual_offset_x_pts')
|
|
1087
1122
|
print(f"Left title offset: X={current_x_px:+.2f} px (positive=left)")
|
|
1088
|
-
sub =
|
|
1123
|
+
sub = _safe_input(_colorize_prompt("left (a=left, d=right, 0=reset, q=back): ")).strip().lower()
|
|
1089
1124
|
if not sub:
|
|
1090
1125
|
continue
|
|
1091
1126
|
if sub == 'q':
|
|
@@ -1116,7 +1151,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1116
1151
|
print(" " + _colorize_menu('d : adjust right title (d=right, a=left, w=up, s=down)'))
|
|
1117
1152
|
print(" " + _colorize_menu('r : reset all offsets'))
|
|
1118
1153
|
print(" " + _colorize_menu('q : return'))
|
|
1119
|
-
choice =
|
|
1154
|
+
choice = _safe_input(_colorize_prompt("p> ")).strip().lower()
|
|
1120
1155
|
if not choice:
|
|
1121
1156
|
continue
|
|
1122
1157
|
if choice == 'q':
|
|
@@ -1587,7 +1622,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1587
1622
|
_print_menu(len(all_cycles), is_dqdv)
|
|
1588
1623
|
while True:
|
|
1589
1624
|
try:
|
|
1590
|
-
key =
|
|
1625
|
+
key = _safe_input("Press a key: ").strip().lower()
|
|
1591
1626
|
except (KeyboardInterrupt, EOFError):
|
|
1592
1627
|
print("\n\nExiting interactive menu...")
|
|
1593
1628
|
break
|
|
@@ -1595,7 +1630,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1595
1630
|
continue
|
|
1596
1631
|
if key == 'q':
|
|
1597
1632
|
try:
|
|
1598
|
-
confirm =
|
|
1633
|
+
confirm = _safe_input(_colorize_prompt("Quit EC interactive? Remember to save (e=export, s=save). Quit now? (y/n): ")).strip().lower()
|
|
1599
1634
|
except Exception:
|
|
1600
1635
|
confirm = 'y'
|
|
1601
1636
|
if confirm == 'y':
|
|
@@ -1630,9 +1665,9 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1630
1665
|
|
|
1631
1666
|
last_figure_path = getattr(fig, '_last_figure_export_path', None)
|
|
1632
1667
|
if last_figure_path:
|
|
1633
|
-
fname =
|
|
1668
|
+
fname = _safe_input("Export filename (default .svg if no extension), number to overwrite, or o to overwrite last (q=cancel): ").strip()
|
|
1634
1669
|
else:
|
|
1635
|
-
fname =
|
|
1670
|
+
fname = _safe_input("Export filename (default .svg if no extension) or number to overwrite (q=cancel): ").strip()
|
|
1636
1671
|
if not fname or fname.lower() == 'q':
|
|
1637
1672
|
_print_menu(len(all_cycles), is_dqdv)
|
|
1638
1673
|
continue
|
|
@@ -1648,7 +1683,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1648
1683
|
print(f"Previous export file not found: {last_figure_path}")
|
|
1649
1684
|
_print_menu(len(all_cycles), is_dqdv)
|
|
1650
1685
|
continue
|
|
1651
|
-
yn =
|
|
1686
|
+
yn = _safe_input(f"Overwrite '{os.path.basename(last_figure_path)}'? (y/n): ").strip().lower()
|
|
1652
1687
|
if yn != 'y':
|
|
1653
1688
|
_print_menu(len(all_cycles), is_dqdv)
|
|
1654
1689
|
continue
|
|
@@ -1660,7 +1695,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1660
1695
|
idx = int(fname)
|
|
1661
1696
|
if 1 <= idx <= len(files):
|
|
1662
1697
|
name = files[idx-1]
|
|
1663
|
-
yn =
|
|
1698
|
+
yn = _safe_input(f"Overwrite '{name}'? (y/n): ").strip().lower()
|
|
1664
1699
|
if yn != 'y':
|
|
1665
1700
|
_print_menu(len(all_cycles), is_dqdv)
|
|
1666
1701
|
continue
|
|
@@ -1798,7 +1833,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1798
1833
|
xy_in = _sanitize_legend_offset(fig, getattr(fig, '_ec_legend_xy_in', (0.0, 0.0))) or (0.0, 0.0)
|
|
1799
1834
|
print(f"Legend is {'ON' if vis else 'off'}; position (inches from center): x={xy_in[0]:.2f}, y={xy_in[1]:.2f}")
|
|
1800
1835
|
while True:
|
|
1801
|
-
sub =
|
|
1836
|
+
sub = _safe_input(_colorize_prompt("Legend: (t=toggle, p=set position, q=back): ")).strip().lower()
|
|
1802
1837
|
if not sub:
|
|
1803
1838
|
continue
|
|
1804
1839
|
if sub == 'q':
|
|
@@ -1822,7 +1857,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1822
1857
|
while True:
|
|
1823
1858
|
xy_in = _sanitize_legend_offset(fig, getattr(fig, '_ec_legend_xy_in', (0.0, 0.0))) or (0.0, 0.0)
|
|
1824
1859
|
print(f"Current position: x={xy_in[0]:.2f}, y={xy_in[1]:.2f}")
|
|
1825
|
-
pos_cmd =
|
|
1860
|
+
pos_cmd = _safe_input(_colorize_prompt("Position: (x y) or x=x only, y=y only, q=back: ")).strip().lower()
|
|
1826
1861
|
if not pos_cmd or pos_cmd == 'q':
|
|
1827
1862
|
break
|
|
1828
1863
|
if pos_cmd == 'x':
|
|
@@ -1830,7 +1865,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1830
1865
|
while True:
|
|
1831
1866
|
xy_in = _sanitize_legend_offset(fig, getattr(fig, '_ec_legend_xy_in', (0.0, 0.0))) or (0.0, 0.0)
|
|
1832
1867
|
print(f"Current position: x={xy_in[0]:.2f}, y={xy_in[1]:.2f}")
|
|
1833
|
-
val =
|
|
1868
|
+
val = _safe_input(f"Enter new x position (current y: {xy_in[1]:.2f}, q=back): ").strip()
|
|
1834
1869
|
if not val or val.lower() == 'q':
|
|
1835
1870
|
break
|
|
1836
1871
|
try:
|
|
@@ -1857,7 +1892,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1857
1892
|
while True:
|
|
1858
1893
|
xy_in = _sanitize_legend_offset(fig, getattr(fig, '_ec_legend_xy_in', (0.0, 0.0))) or (0.0, 0.0)
|
|
1859
1894
|
print(f"Current position: x={xy_in[0]:.2f}, y={xy_in[1]:.2f}")
|
|
1860
|
-
val =
|
|
1895
|
+
val = _safe_input(f"Enter new y position (current x: {xy_in[0]:.2f}, q=back): ").strip()
|
|
1861
1896
|
if not val or val.lower() == 'q':
|
|
1862
1897
|
break
|
|
1863
1898
|
try:
|
|
@@ -1932,9 +1967,9 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1932
1967
|
|
|
1933
1968
|
last_style_path = getattr(fig, '_last_style_export_path', None)
|
|
1934
1969
|
if last_style_path:
|
|
1935
|
-
sub =
|
|
1970
|
+
sub = _safe_input(_colorize_prompt("Style submenu: (e=export, o=overwrite last, q=return, r=refresh): ")).strip().lower()
|
|
1936
1971
|
else:
|
|
1937
|
-
sub =
|
|
1972
|
+
sub = _safe_input(_colorize_prompt("Style submenu: (e=export, q=return, r=refresh): ")).strip().lower()
|
|
1938
1973
|
if sub == 'q':
|
|
1939
1974
|
break
|
|
1940
1975
|
if sub == 'r' or sub == '':
|
|
@@ -1947,7 +1982,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1947
1982
|
if not os.path.exists(last_style_path):
|
|
1948
1983
|
print(f"Previous export file not found: {last_style_path}")
|
|
1949
1984
|
continue
|
|
1950
|
-
yn =
|
|
1985
|
+
yn = _safe_input(f"Overwrite '{os.path.basename(last_style_path)}'? (y/n): ").strip().lower()
|
|
1951
1986
|
if yn != 'y':
|
|
1952
1987
|
continue
|
|
1953
1988
|
# Rebuild config based on current state
|
|
@@ -1974,7 +2009,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1974
2009
|
print("Export options:")
|
|
1975
2010
|
print(" ps = style only (.bps)")
|
|
1976
2011
|
print(" psg = style + geometry (.bpsg)")
|
|
1977
|
-
exp_choice =
|
|
2012
|
+
exp_choice = _safe_input(_colorize_prompt("Export choice (ps/psg, q=cancel): ")).strip().lower()
|
|
1978
2013
|
if not exp_choice or exp_choice == 'q':
|
|
1979
2014
|
print("Style export canceled.")
|
|
1980
2015
|
continue
|
|
@@ -2426,13 +2461,13 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2426
2461
|
print(f" {_colorize_menu('ld : show line and dots (markers) for all curves')}")
|
|
2427
2462
|
print(f" {_colorize_menu('d : show only dots (no connecting line) for all curves')}")
|
|
2428
2463
|
print(f" {_colorize_menu('q : return')}")
|
|
2429
|
-
sub =
|
|
2464
|
+
sub = _safe_input(_colorize_prompt("Choose (c/f/g/l/ld/d/q): ")).strip().lower()
|
|
2430
2465
|
if not sub:
|
|
2431
2466
|
continue
|
|
2432
2467
|
if sub == 'q':
|
|
2433
2468
|
break
|
|
2434
2469
|
if sub == 'c':
|
|
2435
|
-
spec =
|
|
2470
|
+
spec = _safe_input("Curve linewidth (single value for all curves, q=cancel): ").strip()
|
|
2436
2471
|
if not spec or spec.lower() == 'q':
|
|
2437
2472
|
continue
|
|
2438
2473
|
# Apply single width to all curves
|
|
@@ -2461,7 +2496,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2461
2496
|
except ValueError:
|
|
2462
2497
|
print("Invalid width value.")
|
|
2463
2498
|
elif sub == 'f':
|
|
2464
|
-
fw_in =
|
|
2499
|
+
fw_in = _safe_input("Enter frame/tick width (e.g., 1.5) or 'm M' (major minor) or q: ").strip()
|
|
2465
2500
|
if not fw_in or fw_in.lower() == 'q':
|
|
2466
2501
|
print("Canceled.")
|
|
2467
2502
|
continue
|
|
@@ -2536,7 +2571,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2536
2571
|
# Line + dots for all curves
|
|
2537
2572
|
push_state("line+dots")
|
|
2538
2573
|
try:
|
|
2539
|
-
msize_in =
|
|
2574
|
+
msize_in = _safe_input("Marker size (blank=auto ~3*lw): ").strip()
|
|
2540
2575
|
custom_msize = float(msize_in) if msize_in else None
|
|
2541
2576
|
except ValueError:
|
|
2542
2577
|
custom_msize = None
|
|
@@ -2566,7 +2601,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2566
2601
|
# Dots only for all curves
|
|
2567
2602
|
push_state("dots-only")
|
|
2568
2603
|
try:
|
|
2569
|
-
msize_in =
|
|
2604
|
+
msize_in = _safe_input("Marker size (blank=auto ~3*lw): ").strip()
|
|
2570
2605
|
custom_msize = float(msize_in) if msize_in else None
|
|
2571
2606
|
except ValueError:
|
|
2572
2607
|
custom_msize = None
|
|
@@ -2613,7 +2648,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2613
2648
|
print(f" {idx}: {color_block(color)} {color}")
|
|
2614
2649
|
print("Type 'u' to edit saved colors.")
|
|
2615
2650
|
print("q: back to main menu")
|
|
2616
|
-
line =
|
|
2651
|
+
line = _safe_input("Enter mappings (e.g., w:red a:#4561F7) or q: ").strip()
|
|
2617
2652
|
if not line or line.lower() == 'q':
|
|
2618
2653
|
break
|
|
2619
2654
|
if line.lower() == 'u':
|
|
@@ -2664,13 +2699,13 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2664
2699
|
print(" Bullet: $\\bullet$ → • | Greek: $\\alpha$, $\\beta$ | Angstrom: $\\AA$ → Å")
|
|
2665
2700
|
while True:
|
|
2666
2701
|
print("Rename axis: x, y, both, q=back")
|
|
2667
|
-
sub =
|
|
2702
|
+
sub = _safe_input("Rename> ").strip().lower()
|
|
2668
2703
|
if not sub:
|
|
2669
2704
|
continue
|
|
2670
2705
|
if sub == 'q':
|
|
2671
2706
|
break
|
|
2672
2707
|
if sub in ('x','both'):
|
|
2673
|
-
txt =
|
|
2708
|
+
txt = _safe_input("New X-axis label (blank=cancel): ")
|
|
2674
2709
|
if txt:
|
|
2675
2710
|
push_state("rename-x")
|
|
2676
2711
|
try:
|
|
@@ -2693,7 +2728,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2693
2728
|
except Exception:
|
|
2694
2729
|
pass
|
|
2695
2730
|
if sub in ('y','both'):
|
|
2696
|
-
txt =
|
|
2731
|
+
txt = _safe_input("New Y-axis label (blank=cancel): ")
|
|
2697
2732
|
if txt:
|
|
2698
2733
|
push_state("rename-y")
|
|
2699
2734
|
base_ylabel = txt
|
|
@@ -2829,7 +2864,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2829
2864
|
print(_colorize_inline_commands(" 1=spine 2=ticks 3=minor ticks 4=tick labels 5=axis title"))
|
|
2830
2865
|
print(_colorize_inline_commands("Type 'i' to invert tick direction, 'l' to change tick length, 'list' for state, 'q' to return."))
|
|
2831
2866
|
print(_colorize_inline_commands(" p = adjust title offsets (w=top, s=bottom, a=left, d=right)"))
|
|
2832
|
-
cmd =
|
|
2867
|
+
cmd = _safe_input(_colorize_prompt("t> ")).strip().lower()
|
|
2833
2868
|
if not cmd:
|
|
2834
2869
|
continue
|
|
2835
2870
|
if cmd == 'q':
|
|
@@ -2856,7 +2891,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2856
2891
|
# Get current major tick length from axes
|
|
2857
2892
|
current_major = ax.xaxis.get_major_ticks()[0].tick1line.get_markersize() if ax.xaxis.get_major_ticks() else 4.0
|
|
2858
2893
|
print(f"Current major tick length: {current_major}")
|
|
2859
|
-
new_length_str =
|
|
2894
|
+
new_length_str = _safe_input("Enter new major tick length (e.g., 6.0): ").strip()
|
|
2860
2895
|
if not new_length_str:
|
|
2861
2896
|
continue
|
|
2862
2897
|
new_major = float(new_length_str)
|
|
@@ -2944,7 +2979,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2944
2979
|
prompt = "Enter new filename (no ext needed), number to overwrite, or o to overwrite last (q=cancel): "
|
|
2945
2980
|
else:
|
|
2946
2981
|
prompt = "Enter new filename (no ext needed) or number to overwrite (q=cancel): "
|
|
2947
|
-
choice =
|
|
2982
|
+
choice = _safe_input(prompt).strip()
|
|
2948
2983
|
if not choice or choice.lower() == 'q':
|
|
2949
2984
|
_print_menu(len(all_cycles), is_dqdv); continue
|
|
2950
2985
|
if choice.lower() == 'o':
|
|
@@ -2955,7 +2990,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2955
2990
|
if not os.path.exists(last_session_path):
|
|
2956
2991
|
print(f"Previous save file not found: {last_session_path}")
|
|
2957
2992
|
_print_menu(len(all_cycles), is_dqdv); continue
|
|
2958
|
-
yn =
|
|
2993
|
+
yn = _safe_input(f"Overwrite '{os.path.basename(last_session_path)}'? (y/n): ").strip().lower()
|
|
2959
2994
|
if yn != 'y':
|
|
2960
2995
|
_print_menu(len(all_cycles), is_dqdv); continue
|
|
2961
2996
|
dump_ec_session(last_session_path, fig=fig, ax=ax, cycle_lines=cycle_lines, skip_confirm=True)
|
|
@@ -2965,7 +3000,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2965
3000
|
idx = int(choice)
|
|
2966
3001
|
if 1 <= idx <= len(files):
|
|
2967
3002
|
name = files[idx-1]
|
|
2968
|
-
yn =
|
|
3003
|
+
yn = _safe_input(f"Overwrite '{name}'? (y/n): ").strip().lower()
|
|
2969
3004
|
if yn != 'y':
|
|
2970
3005
|
_print_menu(len(all_cycles), is_dqdv); continue
|
|
2971
3006
|
target = os.path.join(folder, name)
|
|
@@ -2982,7 +3017,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2982
3017
|
name = name + '.pkl'
|
|
2983
3018
|
target = name if os.path.isabs(name) else os.path.join(folder, name)
|
|
2984
3019
|
if os.path.exists(target):
|
|
2985
|
-
yn =
|
|
3020
|
+
yn = _safe_input(f"'{os.path.basename(target)}' exists. Overwrite? (y/n): ").strip().lower()
|
|
2986
3021
|
if yn != 'y':
|
|
2987
3022
|
_print_menu(len(all_cycles), is_dqdv); continue
|
|
2988
3023
|
dump_ec_session(target, fig=fig, ax=ax, cycle_lines=cycle_lines, skip_confirm=True)
|
|
@@ -3018,7 +3053,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3018
3053
|
for idx, color in enumerate(user_colors, 1):
|
|
3019
3054
|
print(f" {idx}: {color_block(color)} {color}")
|
|
3020
3055
|
print("Type 'u' to edit saved colors before assigning.")
|
|
3021
|
-
line =
|
|
3056
|
+
line = _safe_input("Selection: ").strip()
|
|
3022
3057
|
if not line:
|
|
3023
3058
|
continue
|
|
3024
3059
|
if line.lower() == 'u':
|
|
@@ -3163,14 +3198,14 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3163
3198
|
# X-axis submenu: number-of-ions vs capacity
|
|
3164
3199
|
while True:
|
|
3165
3200
|
print("X-axis menu: n=number of ions, c=capacity, q=back")
|
|
3166
|
-
sub =
|
|
3201
|
+
sub = _safe_input("X> ").strip().lower()
|
|
3167
3202
|
if not sub:
|
|
3168
3203
|
continue
|
|
3169
3204
|
if sub == 'q':
|
|
3170
3205
|
break
|
|
3171
3206
|
if sub == 'n':
|
|
3172
3207
|
print("Input the theoretical capacity per 1 active ion (mAh g^-1), e.g., 125")
|
|
3173
|
-
val =
|
|
3208
|
+
val = _safe_input("C_theoretical_per_ion: ").strip()
|
|
3174
3209
|
try:
|
|
3175
3210
|
c_th = float(val)
|
|
3176
3211
|
if c_th <= 0:
|
|
@@ -3307,7 +3342,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3307
3342
|
cur_size = plt.rcParams.get('font.size', None)
|
|
3308
3343
|
while True:
|
|
3309
3344
|
print(f"\nFont menu (current: family='{cur_family}', size={cur_size}): f=font family, s=size, q=back")
|
|
3310
|
-
sub =
|
|
3345
|
+
sub = _safe_input("Font> ").strip().lower()
|
|
3311
3346
|
if not sub:
|
|
3312
3347
|
continue
|
|
3313
3348
|
if sub == 'q':
|
|
@@ -3320,7 +3355,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3320
3355
|
for i, font in enumerate(fonts, 1):
|
|
3321
3356
|
print(f" {i}: {font}")
|
|
3322
3357
|
print("Or enter custom font name directly.")
|
|
3323
|
-
choice =
|
|
3358
|
+
choice = _safe_input(f"Font family (current: '{cur_family}', number or name): ").strip()
|
|
3324
3359
|
if not choice:
|
|
3325
3360
|
continue
|
|
3326
3361
|
# Check if it's a number
|
|
@@ -3352,7 +3387,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3352
3387
|
# Show current size and accept direct input
|
|
3353
3388
|
import matplotlib as mpl
|
|
3354
3389
|
cur_size = mpl.rcParams.get('font.size', None)
|
|
3355
|
-
choice =
|
|
3390
|
+
choice = _safe_input(f"Font size (current: {cur_size}): ").strip()
|
|
3356
3391
|
if not choice:
|
|
3357
3392
|
continue
|
|
3358
3393
|
try:
|
|
@@ -3377,7 +3412,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3377
3412
|
while True:
|
|
3378
3413
|
current_xlim = ax.get_xlim()
|
|
3379
3414
|
print(f"Current X range: {current_xlim[0]:.6g} to {current_xlim[1]:.6g}")
|
|
3380
|
-
lim =
|
|
3415
|
+
lim = _safe_input("Set X limits (min max), w=upper only, s=lower only, a=auto (restore original), q=back: ").strip()
|
|
3381
3416
|
if not lim or lim.lower() == 'q':
|
|
3382
3417
|
break
|
|
3383
3418
|
if lim.lower() == 'a':
|
|
@@ -3399,7 +3434,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3399
3434
|
while True:
|
|
3400
3435
|
current_xlim = ax.get_xlim()
|
|
3401
3436
|
print(f"Current X range: {current_xlim[0]:.6g} to {current_xlim[1]:.6g}")
|
|
3402
|
-
val =
|
|
3437
|
+
val = _safe_input(f"Enter new upper X limit (current lower: {current_xlim[0]:.6g}, q=back): ").strip()
|
|
3403
3438
|
if not val or val.lower() == 'q':
|
|
3404
3439
|
break
|
|
3405
3440
|
try:
|
|
@@ -3425,7 +3460,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3425
3460
|
while True:
|
|
3426
3461
|
current_xlim = ax.get_xlim()
|
|
3427
3462
|
print(f"Current X range: {current_xlim[0]:.6g} to {current_xlim[1]:.6g}")
|
|
3428
|
-
val =
|
|
3463
|
+
val = _safe_input(f"Enter new lower X limit (current upper: {current_xlim[1]:.6g}, q=back): ").strip()
|
|
3429
3464
|
if not val or val.lower() == 'q':
|
|
3430
3465
|
break
|
|
3431
3466
|
try:
|
|
@@ -3461,7 +3496,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3461
3496
|
while True:
|
|
3462
3497
|
current_ylim = ax.get_ylim()
|
|
3463
3498
|
print(f"Current Y range: {current_ylim[0]:.6g} to {current_ylim[1]:.6g}")
|
|
3464
|
-
lim =
|
|
3499
|
+
lim = _safe_input("Set Y limits (min max), w=upper only, s=lower only, a=auto (restore original), q=back: ").strip()
|
|
3465
3500
|
if not lim or lim.lower() == 'q':
|
|
3466
3501
|
break
|
|
3467
3502
|
if lim.lower() == 'a':
|
|
@@ -3483,7 +3518,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3483
3518
|
while True:
|
|
3484
3519
|
current_ylim = ax.get_ylim()
|
|
3485
3520
|
print(f"Current Y range: {current_ylim[0]:.6g} to {current_ylim[1]:.6g}")
|
|
3486
|
-
val =
|
|
3521
|
+
val = _safe_input(f"Enter new upper Y limit (current lower: {current_ylim[0]:.6g}, q=back): ").strip()
|
|
3487
3522
|
if not val or val.lower() == 'q':
|
|
3488
3523
|
break
|
|
3489
3524
|
try:
|
|
@@ -3509,7 +3544,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3509
3544
|
while True:
|
|
3510
3545
|
current_ylim = ax.get_ylim()
|
|
3511
3546
|
print(f"Current Y range: {current_ylim[0]:.6g} to {current_ylim[1]:.6g}")
|
|
3512
|
-
val =
|
|
3547
|
+
val = _safe_input(f"Enter new lower Y limit (current upper: {current_ylim[1]:.6g}, q=back): ").strip()
|
|
3513
3548
|
if not val or val.lower() == 'q':
|
|
3514
3549
|
break
|
|
3515
3550
|
try:
|
|
@@ -3544,7 +3579,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3544
3579
|
# Geometry submenu: plot frame vs canvas (scales moved to separate keys)
|
|
3545
3580
|
while True:
|
|
3546
3581
|
print("Geometry menu: p=plot frame size, c=canvas size, q=back")
|
|
3547
|
-
sub =
|
|
3582
|
+
sub = _safe_input("Geom> ").strip().lower()
|
|
3548
3583
|
if not sub:
|
|
3549
3584
|
continue
|
|
3550
3585
|
if sub == 'q':
|
|
@@ -3583,7 +3618,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3583
3618
|
print(" o: remove outliers (removes abrupt dQ/dV spikes)")
|
|
3584
3619
|
print(" r: reset to original data")
|
|
3585
3620
|
print(" q: back to main menu")
|
|
3586
|
-
sub =
|
|
3621
|
+
sub = _safe_input("sm> ").strip().lower()
|
|
3587
3622
|
if not sub:
|
|
3588
3623
|
continue
|
|
3589
3624
|
if sub == 'q':
|
|
@@ -3618,7 +3653,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3618
3653
|
if sub == 'a':
|
|
3619
3654
|
try:
|
|
3620
3655
|
while True:
|
|
3621
|
-
threshold_input =
|
|
3656
|
+
threshold_input = _safe_input("Enter minimum voltage step in mV (default 0.5 mV, 'q'=quit, 'e'=explain): ").strip()
|
|
3622
3657
|
if threshold_input.lower() == 'q':
|
|
3623
3658
|
break
|
|
3624
3659
|
if threshold_input.lower() == 'e':
|
|
@@ -3692,7 +3727,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3692
3727
|
try:
|
|
3693
3728
|
print("DiffCap smoothing per Thompson et al. (2020): clean ΔV < threshold and apply Savitzky–Golay (order 3).")
|
|
3694
3729
|
while True:
|
|
3695
|
-
delta_input =
|
|
3730
|
+
delta_input = _safe_input("Minimum ΔV between points (mV, default 1.0, 'q'=quit, 'e'=explain): ").strip()
|
|
3696
3731
|
if delta_input.lower() == 'q':
|
|
3697
3732
|
break
|
|
3698
3733
|
if delta_input.lower() == 'e':
|
|
@@ -3715,7 +3750,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3715
3750
|
if delta_input and delta_input.lower() == 'q': # User quit at previous step
|
|
3716
3751
|
continue
|
|
3717
3752
|
while True:
|
|
3718
|
-
window_input =
|
|
3753
|
+
window_input = _safe_input("Savitzky–Golay window (odd, default 9, 'q'=quit, 'e'=explain): ").strip()
|
|
3719
3754
|
if window_input.lower() == 'q':
|
|
3720
3755
|
break
|
|
3721
3756
|
if window_input.lower() == 'e':
|
|
@@ -3734,7 +3769,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3734
3769
|
if window_input and window_input.lower() == 'q': # User quit at previous step
|
|
3735
3770
|
continue
|
|
3736
3771
|
while True:
|
|
3737
|
-
poly_input =
|
|
3772
|
+
poly_input = _safe_input("Polynomial order (default 3, 'q'=quit, 'e'=explain): ").strip()
|
|
3738
3773
|
if poly_input.lower() == 'q':
|
|
3739
3774
|
break
|
|
3740
3775
|
if poly_input.lower() == 'e':
|
|
@@ -3806,7 +3841,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3806
3841
|
print(" 1: Z-score (enter standard deviation threshold, default 5.0)")
|
|
3807
3842
|
print(" 2: MAD (median absolute deviation, default factor 6.0)")
|
|
3808
3843
|
while True:
|
|
3809
|
-
method =
|
|
3844
|
+
method = _safe_input("Method (1/2, blank=cancel, 'q'=quit, 'e'=explain): ").strip()
|
|
3810
3845
|
if not method or method.lower() == 'q':
|
|
3811
3846
|
break
|
|
3812
3847
|
if method.lower() == 'e':
|
|
@@ -3832,7 +3867,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3832
3867
|
continue
|
|
3833
3868
|
try:
|
|
3834
3869
|
while True:
|
|
3835
|
-
thresh_input =
|
|
3870
|
+
thresh_input = _safe_input("Enter threshold (blank=default, 'q'=quit, 'e'=explain): ").strip()
|
|
3836
3871
|
if thresh_input.lower() == 'q':
|
|
3837
3872
|
break
|
|
3838
3873
|
if thresh_input.lower() == 'e':
|
|
@@ -4364,7 +4399,7 @@ def _export_style_dialog(cfg: Dict, default_ext: str = '.bpcfg', base_path: Opti
|
|
|
4364
4399
|
for i, f in enumerate(bpcfg_files, 1):
|
|
4365
4400
|
print(f" {i}: {f}")
|
|
4366
4401
|
|
|
4367
|
-
choice =
|
|
4402
|
+
choice = _safe_input(f"Export to file? Enter filename or number to overwrite (q=cancel): ").strip()
|
|
4368
4403
|
if not choice or choice.lower() == 'q':
|
|
4369
4404
|
return
|
|
4370
4405
|
|