batplot 1.7.27__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 +209 -73
- {batplot-1.7.27.dist-info → batplot-1.8.0.dist-info}/METADATA +1 -1
- {batplot-1.7.27.dist-info → batplot-1.8.0.dist-info}/RECORD +12 -12
- {batplot-1.7.27.dist-info → batplot-1.8.0.dist-info}/WHEEL +0 -0
- {batplot-1.7.27.dist-info → batplot-1.8.0.dist-info}/entry_points.txt +0 -0
- {batplot-1.7.27.dist-info → batplot-1.8.0.dist-info}/licenses/LICENSE +0 -0
- {batplot-1.7.27.dist-info → batplot-1.8.0.dist-info}/top_level.txt +0 -0
|
@@ -18,6 +18,7 @@ from typing import Tuple, Dict, Optional, Any
|
|
|
18
18
|
import json
|
|
19
19
|
import os
|
|
20
20
|
import time
|
|
21
|
+
import sys
|
|
21
22
|
|
|
22
23
|
import matplotlib.pyplot as plt
|
|
23
24
|
from matplotlib.colors import LinearSegmentedColormap
|
|
@@ -41,6 +42,37 @@ from .color_utils import (
|
|
|
41
42
|
from .utils import choose_style_file
|
|
42
43
|
|
|
43
44
|
|
|
45
|
+
class _FilterIMKWarning:
|
|
46
|
+
"""Filter that suppresses macOS IMKCFRunLoopWakeUpReliable warnings while preserving other errors."""
|
|
47
|
+
def __init__(self, original_stderr):
|
|
48
|
+
self.original_stderr = original_stderr
|
|
49
|
+
|
|
50
|
+
def write(self, message):
|
|
51
|
+
# Filter out the harmless macOS IMK warning
|
|
52
|
+
if 'IMKCFRunLoopWakeUpReliable' not in message:
|
|
53
|
+
self.original_stderr.write(message)
|
|
54
|
+
|
|
55
|
+
def flush(self):
|
|
56
|
+
self.original_stderr.flush()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _safe_input(prompt: str = "") -> str:
|
|
60
|
+
"""Wrapper around input() that suppresses macOS IMKCFRunLoopWakeUpReliable warnings.
|
|
61
|
+
|
|
62
|
+
This is a harmless macOS system message that appears when using input() in terminals.
|
|
63
|
+
"""
|
|
64
|
+
# Filter stderr to hide macOS IMK warnings while preserving other errors
|
|
65
|
+
original_stderr = sys.stderr
|
|
66
|
+
sys.stderr = _FilterIMKWarning(original_stderr)
|
|
67
|
+
try:
|
|
68
|
+
result = input(prompt)
|
|
69
|
+
return result
|
|
70
|
+
except (KeyboardInterrupt, EOFError):
|
|
71
|
+
raise
|
|
72
|
+
finally:
|
|
73
|
+
sys.stderr = original_stderr
|
|
74
|
+
|
|
75
|
+
|
|
44
76
|
def _axis_tick_width(axis_obj, which: str = 'major'):
|
|
45
77
|
"""Return tick line width from axis tick params or rc defaults."""
|
|
46
78
|
try:
|
|
@@ -344,6 +376,94 @@ def _update_custom_colorbar(cbar_ax, im=None, label=None, label_mode=None):
|
|
|
344
376
|
_draw_custom_colorbar(cbar_ax, im, label, label_mode)
|
|
345
377
|
|
|
346
378
|
|
|
379
|
+
def _detach_mpl_colorbar_callbacks(cbar, im) -> None:
|
|
380
|
+
"""Detach a Matplotlib Colorbar from its mappable callbacks.
|
|
381
|
+
|
|
382
|
+
Why this exists:
|
|
383
|
+
- In this interactive menu we draw a *custom* colorbar by clearing/redrawing `cbar.ax`.
|
|
384
|
+
- If `cbar` is a real `matplotlib.colorbar.Colorbar` (e.g., loaded from a session),
|
|
385
|
+
it remains connected to `im` via `im.callbacksSM`. Subsequent `im.set_clim()` /
|
|
386
|
+
`im.set_cmap()` triggers `Colorbar.update_normal()`, which can crash after we
|
|
387
|
+
cleared/redrew the axes (observed as: NotImplementedError: cannot remove artist).
|
|
388
|
+
- We therefore disconnect that callback once and always update the custom colorbar
|
|
389
|
+
via `_update_custom_colorbar(...)`.
|
|
390
|
+
"""
|
|
391
|
+
try:
|
|
392
|
+
if cbar is None or im is None:
|
|
393
|
+
return
|
|
394
|
+
cax = getattr(cbar, 'ax', None)
|
|
395
|
+
if cax is not None and getattr(cax, '_bp_detached_mpl_colorbar', False):
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
# APPROACH 1: Try to find and disconnect the callback ID
|
|
399
|
+
cid = None
|
|
400
|
+
for attr in ('_cid', '_cid_colorbar', 'cid'):
|
|
401
|
+
try:
|
|
402
|
+
v = getattr(cbar, attr, None)
|
|
403
|
+
if isinstance(v, int):
|
|
404
|
+
cid = v
|
|
405
|
+
break
|
|
406
|
+
except Exception:
|
|
407
|
+
pass
|
|
408
|
+
|
|
409
|
+
if cid is not None:
|
|
410
|
+
try:
|
|
411
|
+
cbreg = getattr(im, 'callbacksSM', None)
|
|
412
|
+
if cbreg is not None and hasattr(cbreg, 'disconnect'):
|
|
413
|
+
cbreg.disconnect(cid)
|
|
414
|
+
except Exception:
|
|
415
|
+
pass
|
|
416
|
+
|
|
417
|
+
# APPROACH 2: Disconnect ALL callbacks from the image (nuclear option)
|
|
418
|
+
try:
|
|
419
|
+
cbreg = getattr(im, 'callbacksSM', None)
|
|
420
|
+
if cbreg is not None:
|
|
421
|
+
# Try to clear all callbacks
|
|
422
|
+
if hasattr(cbreg, 'callbacks'):
|
|
423
|
+
try:
|
|
424
|
+
cbreg.callbacks.clear()
|
|
425
|
+
except Exception:
|
|
426
|
+
pass
|
|
427
|
+
# Also try the _signals dict if it exists
|
|
428
|
+
if hasattr(cbreg, '_signals'):
|
|
429
|
+
try:
|
|
430
|
+
for signal_dict in cbreg._signals.values():
|
|
431
|
+
if hasattr(signal_dict, 'clear'):
|
|
432
|
+
signal_dict.clear()
|
|
433
|
+
except Exception:
|
|
434
|
+
pass
|
|
435
|
+
except Exception:
|
|
436
|
+
pass
|
|
437
|
+
|
|
438
|
+
# APPROACH 3: Monkey-patch the update_normal method to be a no-op
|
|
439
|
+
# This is the most reliable approach for preventing the callback
|
|
440
|
+
try:
|
|
441
|
+
if hasattr(cbar, 'update_normal'):
|
|
442
|
+
def _noop_update(*args, **kwargs):
|
|
443
|
+
pass
|
|
444
|
+
cbar.update_normal = _noop_update
|
|
445
|
+
except Exception:
|
|
446
|
+
pass
|
|
447
|
+
|
|
448
|
+
# APPROACH 4: Prevent future built-in updates by nulling internal state
|
|
449
|
+
try:
|
|
450
|
+
if hasattr(cbar, 'mappable'):
|
|
451
|
+
cbar.mappable = None
|
|
452
|
+
except Exception:
|
|
453
|
+
pass
|
|
454
|
+
try:
|
|
455
|
+
if hasattr(cbar, 'solids'):
|
|
456
|
+
cbar.solids = None
|
|
457
|
+
except Exception:
|
|
458
|
+
pass
|
|
459
|
+
|
|
460
|
+
if cax is not None:
|
|
461
|
+
setattr(cax, '_bp_detached_mpl_colorbar', True)
|
|
462
|
+
except Exception:
|
|
463
|
+
# Never let detaching break the interactive menu.
|
|
464
|
+
return
|
|
465
|
+
|
|
466
|
+
|
|
347
467
|
def _ensure_fixed_params(fig, ax, cbar_ax, ec_ax):
|
|
348
468
|
"""Initialize and return fixed geometry parameters in inches.
|
|
349
469
|
|
|
@@ -540,6 +660,11 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
540
660
|
# Normalize file path list for downstream helpers
|
|
541
661
|
file_paths = list(file_paths) if file_paths else []
|
|
542
662
|
|
|
663
|
+
# If we were given a real Matplotlib Colorbar (e.g. from session load),
|
|
664
|
+
# detach it from `im` immediately. This must happen before any function
|
|
665
|
+
# that may clear/redraw `cbar.ax` (custom colorbar) is called.
|
|
666
|
+
_detach_mpl_colorbar_callbacks(cbar, im)
|
|
667
|
+
|
|
543
668
|
def _renormalize_to_visible():
|
|
544
669
|
"""Adjust color scale to match the intensity range of the currently visible region.
|
|
545
670
|
|
|
@@ -904,6 +1029,9 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
904
1029
|
# Initialize custom colorbar (replaces matplotlib's colorbar)
|
|
905
1030
|
cbar_label = getattr(cbar.ax, '_colorbar_label', 'Intensity')
|
|
906
1031
|
cbar_label_mode = getattr(fig, '_colorbar_label_mode', 'normal')
|
|
1032
|
+
# If we were given a real Matplotlib Colorbar (e.g. from session load),
|
|
1033
|
+
# detach it from `im` before we clear/redraw the axes for the custom colorbar.
|
|
1034
|
+
_detach_mpl_colorbar_callbacks(cbar, im)
|
|
907
1035
|
_draw_custom_colorbar(cbar.ax, im, cbar_label, cbar_label_mode)
|
|
908
1036
|
# Decrease distance between operando and EC plots once per session
|
|
909
1037
|
if not getattr(ec_ax, '_ec_gap_adjusted', False):
|
|
@@ -1204,7 +1332,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1204
1332
|
if snap.get('cmap'):
|
|
1205
1333
|
im.set_cmap(snap['cmap'])
|
|
1206
1334
|
if cbar is not None:
|
|
1207
|
-
cbar.
|
|
1335
|
+
_update_custom_colorbar(cbar.ax, im)
|
|
1208
1336
|
except Exception:
|
|
1209
1337
|
pass
|
|
1210
1338
|
# Restore colorbar side (ticks/label) and redraw custom colorbar to keep position
|
|
@@ -1667,7 +1795,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1667
1795
|
print("Crosshair OFF.")
|
|
1668
1796
|
while True:
|
|
1669
1797
|
try:
|
|
1670
|
-
cmd =
|
|
1798
|
+
cmd = _safe_input("Press a key: ").strip().lower()
|
|
1671
1799
|
except (KeyboardInterrupt, EOFError):
|
|
1672
1800
|
print("\n\nExiting interactive menu...")
|
|
1673
1801
|
break
|
|
@@ -1675,7 +1803,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1675
1803
|
continue
|
|
1676
1804
|
if cmd == 'q':
|
|
1677
1805
|
try:
|
|
1678
|
-
ans =
|
|
1806
|
+
ans = _safe_input("Quit interactive? Remember to save (e=export, s=save). Quit now? (y/n): ").strip().lower()
|
|
1679
1807
|
except Exception:
|
|
1680
1808
|
ans = 'y'
|
|
1681
1809
|
if ans == 'y':
|
|
@@ -1718,9 +1846,9 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1718
1846
|
|
|
1719
1847
|
last_figure_path = getattr(fig, '_last_figure_export_path', None)
|
|
1720
1848
|
if last_figure_path:
|
|
1721
|
-
fname =
|
|
1849
|
+
fname = _safe_input("Export filename (default .svg if no extension), number to overwrite, or o to overwrite last (q=cancel): ").strip()
|
|
1722
1850
|
else:
|
|
1723
|
-
fname =
|
|
1851
|
+
fname = _safe_input("Export filename (default .svg if no extension) or number to overwrite (q=cancel): ").strip()
|
|
1724
1852
|
if not fname or fname.lower() == 'q':
|
|
1725
1853
|
print_menu(); continue
|
|
1726
1854
|
|
|
@@ -1733,7 +1861,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1733
1861
|
if not os.path.exists(last_figure_path):
|
|
1734
1862
|
print(f"Previous export file not found: {last_figure_path}")
|
|
1735
1863
|
print_menu(); continue
|
|
1736
|
-
yn =
|
|
1864
|
+
yn = _safe_input(f"Overwrite '{os.path.basename(last_figure_path)}'? (y/n): ").strip().lower()
|
|
1737
1865
|
if yn != 'y':
|
|
1738
1866
|
print_menu(); continue
|
|
1739
1867
|
target = last_figure_path
|
|
@@ -1744,7 +1872,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1744
1872
|
idx = int(fname)
|
|
1745
1873
|
if 1 <= idx <= len(files):
|
|
1746
1874
|
name = files[idx-1]
|
|
1747
|
-
yn =
|
|
1875
|
+
yn = _safe_input(f"Overwrite '{name}'? (y/n): ").strip().lower()
|
|
1748
1876
|
if yn != 'y':
|
|
1749
1877
|
print_menu(); continue
|
|
1750
1878
|
target = file_list[idx-1][1] # Full path from list
|
|
@@ -1823,7 +1951,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1823
1951
|
cb_h_offset = getattr(cbar.ax, '_cb_h_offset_in', 0.0)
|
|
1824
1952
|
ec_h_offset = getattr(ec_ax, '_ec_h_offset_in', 0.0)
|
|
1825
1953
|
print(f"Toggle: 1=colorbar, 2=EC panel, 3=both, 4=colorbar label mode, 5=colorbar label text, m=move horizontal position (cb:{cb_h_offset:.3f}\", ec:{ec_h_offset:.3f}\"), q=cancel")
|
|
1826
|
-
choice =
|
|
1954
|
+
choice = _safe_input("v> ").strip().lower()
|
|
1827
1955
|
if choice == '1':
|
|
1828
1956
|
# Toggle colorbar
|
|
1829
1957
|
cb_vis = cbar.ax.get_visible()
|
|
@@ -1857,7 +1985,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1857
1985
|
# Change colorbar label text
|
|
1858
1986
|
current_label = getattr(cbar.ax, '_colorbar_label', 'Intensity')
|
|
1859
1987
|
print(f"Current colorbar label: {current_label}")
|
|
1860
|
-
new_label =
|
|
1988
|
+
new_label = _safe_input("New colorbar label (blank to keep): ").strip()
|
|
1861
1989
|
if new_label:
|
|
1862
1990
|
cbar.ax._colorbar_label = new_label
|
|
1863
1991
|
try:
|
|
@@ -1878,12 +2006,12 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1878
2006
|
if ec_ax is not None:
|
|
1879
2007
|
print(f" EC panel offset: {ec_h_offset:.3f}\" (positive=right, negative=left)")
|
|
1880
2008
|
print("Commands: c=colorbar, e=EC panel, q=back")
|
|
1881
|
-
sub =
|
|
2009
|
+
sub = _safe_input("m> ").strip().lower()
|
|
1882
2010
|
if not sub or sub == 'q':
|
|
1883
2011
|
break
|
|
1884
2012
|
if sub == 'c':
|
|
1885
2013
|
try:
|
|
1886
|
-
new_offset =
|
|
2014
|
+
new_offset = _safe_input(f"Enter colorbar horizontal offset in inches (current: {cb_h_offset:.3f}\"): ").strip()
|
|
1887
2015
|
if new_offset:
|
|
1888
2016
|
cb_h_offset = float(new_offset)
|
|
1889
2017
|
setattr(cbar.ax, '_cb_h_offset_in', cb_h_offset)
|
|
@@ -1891,6 +2019,8 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1891
2019
|
_apply_group_layout_inches(fig, ax, cbar.ax, ec_ax, ax_w_in, ax_h_in, cb_w_in, cb_gap_in, ec_gap_in, ec_w_in)
|
|
1892
2020
|
fig.canvas.draw_idle()
|
|
1893
2021
|
print(f"Colorbar horizontal offset set to {cb_h_offset:.3f}\"")
|
|
2022
|
+
# Continue in loop to show menu again
|
|
2023
|
+
continue
|
|
1894
2024
|
except ValueError:
|
|
1895
2025
|
print("Invalid input. Enter a number.")
|
|
1896
2026
|
except Exception as e:
|
|
@@ -1900,7 +2030,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1900
2030
|
print("EC panel not available.")
|
|
1901
2031
|
continue
|
|
1902
2032
|
try:
|
|
1903
|
-
new_offset =
|
|
2033
|
+
new_offset = _safe_input(f"Enter EC panel horizontal offset in inches (current: {ec_h_offset:.3f}\"): ").strip()
|
|
1904
2034
|
if new_offset:
|
|
1905
2035
|
ec_h_offset = float(new_offset)
|
|
1906
2036
|
setattr(ec_ax, '_ec_h_offset_in', ec_h_offset)
|
|
@@ -1908,6 +2038,8 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1908
2038
|
_apply_group_layout_inches(fig, ax, cbar.ax, ec_ax, ax_w_in, ax_h_in, cb_w_in, cb_gap_in, ec_gap_in, ec_w_in)
|
|
1909
2039
|
fig.canvas.draw_idle()
|
|
1910
2040
|
print(f"EC panel horizontal offset set to {ec_h_offset:.3f}\"")
|
|
2041
|
+
# Continue in loop to show menu again
|
|
2042
|
+
continue
|
|
1911
2043
|
except ValueError:
|
|
1912
2044
|
print("Invalid input. Enter a number.")
|
|
1913
2045
|
except Exception as e:
|
|
@@ -1920,7 +2052,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1920
2052
|
# Operando-only mode: toggle colorbar or change label mode
|
|
1921
2053
|
cb_h_offset = getattr(cbar.ax, '_cb_h_offset_in', 0.0)
|
|
1922
2054
|
print(f"Toggle: 1=colorbar visibility, 2=colorbar label mode, 3=colorbar label text, m=move horizontal position (cb:{cb_h_offset:.3f}\"), q=cancel")
|
|
1923
|
-
choice =
|
|
2055
|
+
choice = _safe_input("v> ").strip().lower()
|
|
1924
2056
|
if choice == '1':
|
|
1925
2057
|
cb_vis = cbar.ax.get_visible()
|
|
1926
2058
|
cbar.ax.set_visible(not cb_vis)
|
|
@@ -1940,7 +2072,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1940
2072
|
# Change colorbar label text
|
|
1941
2073
|
current_label = getattr(cbar.ax, '_colorbar_label', 'Intensity')
|
|
1942
2074
|
print(f"Current colorbar label: {current_label}")
|
|
1943
|
-
new_label =
|
|
2075
|
+
new_label = _safe_input("New colorbar label (blank to keep): ").strip()
|
|
1944
2076
|
if new_label:
|
|
1945
2077
|
cbar.ax._colorbar_label = new_label
|
|
1946
2078
|
try:
|
|
@@ -1958,12 +2090,12 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1958
2090
|
print(f"\nHorizontal position (relative to canvas center):")
|
|
1959
2091
|
print(f" Colorbar offset: {cb_h_offset:.3f}\" (positive=right, negative=left)")
|
|
1960
2092
|
print("Commands: c=colorbar, q=back")
|
|
1961
|
-
sub =
|
|
2093
|
+
sub = _safe_input("m> ").strip().lower()
|
|
1962
2094
|
if not sub or sub == 'q':
|
|
1963
2095
|
break
|
|
1964
2096
|
if sub == 'c':
|
|
1965
2097
|
try:
|
|
1966
|
-
new_offset =
|
|
2098
|
+
new_offset = _safe_input(f"Enter colorbar horizontal offset in inches (current: {cb_h_offset:.3f}\"): ").strip()
|
|
1967
2099
|
if new_offset:
|
|
1968
2100
|
cb_h_offset = float(new_offset)
|
|
1969
2101
|
setattr(cbar.ax, '_cb_h_offset_in', cb_h_offset)
|
|
@@ -1971,6 +2103,8 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1971
2103
|
_apply_group_layout_inches(fig, ax, cbar.ax, ec_ax, ax_w_in, ax_h_in, cb_w_in, cb_gap_in, ec_gap_in, ec_w_in)
|
|
1972
2104
|
fig.canvas.draw_idle()
|
|
1973
2105
|
print(f"Colorbar horizontal offset set to {cb_h_offset:.3f}\"")
|
|
2106
|
+
# Continue in loop to show menu again
|
|
2107
|
+
continue
|
|
1974
2108
|
except ValueError:
|
|
1975
2109
|
print("Invalid input. Enter a number.")
|
|
1976
2110
|
except Exception as e:
|
|
@@ -2013,7 +2147,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2013
2147
|
prompt = "Enter new filename (no ext needed), number to overwrite, or o to overwrite last (q=cancel): "
|
|
2014
2148
|
else:
|
|
2015
2149
|
prompt = "Enter new filename (no ext needed) or number to overwrite (q=cancel): "
|
|
2016
|
-
choice =
|
|
2150
|
+
choice = _safe_input(prompt).strip()
|
|
2017
2151
|
if not choice or choice.lower() == 'q':
|
|
2018
2152
|
print_menu(); continue
|
|
2019
2153
|
if choice.lower() == 'o':
|
|
@@ -2024,7 +2158,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2024
2158
|
if not os.path.exists(last_session_path):
|
|
2025
2159
|
print(f"Previous save file not found: {last_session_path}")
|
|
2026
2160
|
print_menu(); continue
|
|
2027
|
-
yn =
|
|
2161
|
+
yn = _safe_input(f"Overwrite '{os.path.basename(last_session_path)}'? (y/n): ").strip().lower()
|
|
2028
2162
|
if yn != 'y':
|
|
2029
2163
|
print_menu(); continue
|
|
2030
2164
|
dump_operando_session(last_session_path, fig=fig, ax=ax, im=im, cbar=cbar, ec_ax=ec_ax, skip_confirm=True)
|
|
@@ -2034,7 +2168,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2034
2168
|
idx = int(choice)
|
|
2035
2169
|
if 1 <= idx <= len(files):
|
|
2036
2170
|
name = files[idx-1]
|
|
2037
|
-
yn =
|
|
2171
|
+
yn = _safe_input(f"Overwrite '{name}'? (y/n): ").strip().lower()
|
|
2038
2172
|
if yn != 'y':
|
|
2039
2173
|
print_menu(); continue
|
|
2040
2174
|
target = os.path.join(folder, name)
|
|
@@ -2051,7 +2185,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2051
2185
|
name = name + '.pkl'
|
|
2052
2186
|
target = name if os.path.isabs(name) else os.path.join(folder, name)
|
|
2053
2187
|
if os.path.exists(target):
|
|
2054
|
-
yn =
|
|
2188
|
+
yn = _safe_input(f"'{os.path.basename(target)}' exists. Overwrite? (y/n): ").strip().lower()
|
|
2055
2189
|
if yn != 'y':
|
|
2056
2190
|
print_menu(); continue
|
|
2057
2191
|
dump_operando_session(target, fig=fig, ax=ax, im=im, cbar=cbar, ec_ax=ec_ax, skip_confirm=True)
|
|
@@ -2076,7 +2210,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2076
2210
|
# Always read fresh value from attribute to avoid stale cached value
|
|
2077
2211
|
ax_h_in = getattr(ax, '_fixed_ax_h_in', ax_h_in)
|
|
2078
2212
|
print(f"Current height: {ax_h_in:.2f} in")
|
|
2079
|
-
val =
|
|
2213
|
+
val = _safe_input("New height (inches): ").strip()
|
|
2080
2214
|
if val:
|
|
2081
2215
|
_snapshot("height")
|
|
2082
2216
|
try:
|
|
@@ -2116,7 +2250,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2116
2250
|
print(f"\nFont submenu (current: family='{cur_family}', size={cur_size})")
|
|
2117
2251
|
print(" f: change family | s: change size | q: back")
|
|
2118
2252
|
while True:
|
|
2119
|
-
sub =
|
|
2253
|
+
sub = _safe_input("Font> ").strip().lower()
|
|
2120
2254
|
if not sub:
|
|
2121
2255
|
continue
|
|
2122
2256
|
if sub == 'q':
|
|
@@ -2129,7 +2263,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2129
2263
|
for i, font in enumerate(fonts, 1):
|
|
2130
2264
|
print(f" {i}: {font}")
|
|
2131
2265
|
print("Or enter custom font name directly.")
|
|
2132
|
-
choice =
|
|
2266
|
+
choice = _safe_input("Font family (number or name): ").strip()
|
|
2133
2267
|
if not choice:
|
|
2134
2268
|
continue
|
|
2135
2269
|
_snapshot("font-family")
|
|
@@ -2149,7 +2283,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2149
2283
|
elif sub == 's':
|
|
2150
2284
|
# Show current size and accept direct input
|
|
2151
2285
|
cur_size = plt.rcParams.get('font.size', None)
|
|
2152
|
-
choice =
|
|
2286
|
+
choice = _safe_input(f"Font size (current: {cur_size}): ").strip()
|
|
2153
2287
|
if not choice:
|
|
2154
2288
|
continue
|
|
2155
2289
|
_snapshot("font-size")
|
|
@@ -2172,7 +2306,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2172
2306
|
print(_colorize_inline_commands(" 1.5 2.5 - set frame=1.5, ticks=2.5"))
|
|
2173
2307
|
print(_colorize_inline_commands(" q - cancel"))
|
|
2174
2308
|
|
|
2175
|
-
inp =
|
|
2309
|
+
inp = _safe_input("Line widths> ").strip().lower()
|
|
2176
2310
|
if not inp or inp == 'q':
|
|
2177
2311
|
print_menu()
|
|
2178
2312
|
continue
|
|
@@ -2235,6 +2369,8 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2235
2369
|
# Unified WASD ticks/labels/spines submenu for either pane
|
|
2236
2370
|
# Import here to avoid scoping issues with nested functions
|
|
2237
2371
|
from matplotlib.ticker import AutoMinorLocator, NullFormatter, MaxNLocator, NullLocator
|
|
2372
|
+
# Import UI positioning functions locally to ensure they're accessible in nested functions
|
|
2373
|
+
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
|
|
2238
2374
|
|
|
2239
2375
|
def _get_tick_state(a):
|
|
2240
2376
|
# Unified keys with fallbacks for legacy combined flags
|
|
@@ -2320,7 +2456,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2320
2456
|
print(_colorize_inline_commands("Choose pane: o=operando, e=ec, q=back"))
|
|
2321
2457
|
else:
|
|
2322
2458
|
print(_colorize_inline_commands("Choose pane: o=operando, q=back"))
|
|
2323
|
-
pane =
|
|
2459
|
+
pane = _safe_input("ot> ").strip().lower()
|
|
2324
2460
|
if not pane:
|
|
2325
2461
|
continue
|
|
2326
2462
|
if pane == 'q':
|
|
@@ -2539,7 +2675,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2539
2675
|
print(_colorize_inline_commands("Type 'i' to invert tick direction, 'l' to change tick length, 'list' for state, 'q' to return."))
|
|
2540
2676
|
print(_colorize_inline_commands(" p = adjust title offsets (w=top, s=bottom, a=left, d=right)"))
|
|
2541
2677
|
while True:
|
|
2542
|
-
cmd2 =
|
|
2678
|
+
cmd2 = _safe_input(_colorize_prompt("Toggle> ")).strip().lower()
|
|
2543
2679
|
if not cmd2:
|
|
2544
2680
|
continue
|
|
2545
2681
|
if cmd2 == 'q':
|
|
@@ -2564,7 +2700,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2564
2700
|
# Get current major tick length from axes
|
|
2565
2701
|
current_major = ax.xaxis.get_major_ticks()[0].tick1line.get_markersize() if ax.xaxis.get_major_ticks() else 4.0
|
|
2566
2702
|
print(f"Current major tick length: {current_major}")
|
|
2567
|
-
new_length_str =
|
|
2703
|
+
new_length_str = _safe_input("Enter new major tick length (e.g., 6.0): ").strip()
|
|
2568
2704
|
if not new_length_str:
|
|
2569
2705
|
continue
|
|
2570
2706
|
new_major = float(new_length_str)
|
|
@@ -2657,7 +2793,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2657
2793
|
current_y_px = _px_value('_top_xlabel_manual_offset_y_pts', target)
|
|
2658
2794
|
current_x_px = _px_value('_top_xlabel_manual_offset_x_pts', target)
|
|
2659
2795
|
print(f"Top title offset: Y={current_y_px:+.2f} px (positive=up), X={current_x_px:+.2f} px (positive=right)")
|
|
2660
|
-
sub =
|
|
2796
|
+
sub = _safe_input(_colorize_prompt("top (w=up, s=down, a=left, d=right, 0=reset, q=back): ")).strip().lower()
|
|
2661
2797
|
if not sub:
|
|
2662
2798
|
continue
|
|
2663
2799
|
if sub == 'q':
|
|
@@ -2694,7 +2830,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2694
2830
|
while True:
|
|
2695
2831
|
current_y_px = _px_value('_bottom_xlabel_manual_offset_y_pts', target)
|
|
2696
2832
|
print(f"Bottom title offset: Y={current_y_px:+.2f} px (positive=down)")
|
|
2697
|
-
sub =
|
|
2833
|
+
sub = _safe_input(_colorize_prompt("bottom (s=down, w=up, 0=reset, q=back): ")).strip().lower()
|
|
2698
2834
|
if not sub:
|
|
2699
2835
|
continue
|
|
2700
2836
|
if sub == 'q':
|
|
@@ -2724,7 +2860,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2724
2860
|
while True:
|
|
2725
2861
|
current_x_px = _px_value('_left_ylabel_manual_offset_x_pts', target)
|
|
2726
2862
|
print(f"Left title offset: X={current_x_px:+.2f} px (positive=left)")
|
|
2727
|
-
sub =
|
|
2863
|
+
sub = _safe_input(_colorize_prompt("left (a=left, d=right, 0=reset, q=back): ")).strip().lower()
|
|
2728
2864
|
if not sub:
|
|
2729
2865
|
continue
|
|
2730
2866
|
if sub == 'q':
|
|
@@ -2761,7 +2897,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2761
2897
|
current_x_px = _px_value('_right_ylabel_manual_offset_x_pts', target)
|
|
2762
2898
|
current_y_px = _px_value('_right_ylabel_manual_offset_y_pts', target)
|
|
2763
2899
|
print(f"Right title offset: X={current_x_px:+.2f} px (positive=right), Y={current_y_px:+.2f} px (positive=up)")
|
|
2764
|
-
sub =
|
|
2900
|
+
sub = _safe_input(_colorize_prompt("right (d=right, a=left, w=up, s=down, 0=reset, q=back): ")).strip().lower()
|
|
2765
2901
|
if not sub:
|
|
2766
2902
|
continue
|
|
2767
2903
|
if sub == 'q':
|
|
@@ -2800,7 +2936,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2800
2936
|
print(" " + _colorize_menu('d : adjust right title (d=right, a=left, w=up, s=down)'))
|
|
2801
2937
|
print(" " + _colorize_menu('r : reset all offsets'))
|
|
2802
2938
|
print(" " + _colorize_menu('q : return'))
|
|
2803
|
-
choice =
|
|
2939
|
+
choice = _safe_input(_colorize_prompt("p> ")).strip().lower()
|
|
2804
2940
|
if not choice:
|
|
2805
2941
|
continue
|
|
2806
2942
|
if choice == 'q':
|
|
@@ -2916,7 +3052,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2916
3052
|
elif cmd == 'ox':
|
|
2917
3053
|
while True:
|
|
2918
3054
|
cur = ax.get_xlim(); print(f"Current operando X: {cur[0]:.4g} {cur[1]:.4g}")
|
|
2919
|
-
line =
|
|
3055
|
+
line = _safe_input("New X range (min max), w=upper only, s=lower only, a=auto (restore original), q=back: ").strip()
|
|
2920
3056
|
if not line or line.lower() == 'q':
|
|
2921
3057
|
break
|
|
2922
3058
|
if line.lower() == 'w':
|
|
@@ -2924,7 +3060,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2924
3060
|
while True:
|
|
2925
3061
|
cur = ax.get_xlim()
|
|
2926
3062
|
print(f"Current operando X: {cur[0]:.4g} {cur[1]:.4g}")
|
|
2927
|
-
val =
|
|
3063
|
+
val = _safe_input(f"Enter new upper X limit (current lower: {cur[0]:.4g}, q=back): ").strip()
|
|
2928
3064
|
if not val or val.lower() == 'q':
|
|
2929
3065
|
break
|
|
2930
3066
|
try:
|
|
@@ -2943,7 +3079,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2943
3079
|
while True:
|
|
2944
3080
|
cur = ax.get_xlim()
|
|
2945
3081
|
print(f"Current operando X: {cur[0]:.4g} {cur[1]:.4g}")
|
|
2946
|
-
val =
|
|
3082
|
+
val = _safe_input(f"Enter new lower X limit (current upper: {cur[1]:.4g}, q=back): ").strip()
|
|
2947
3083
|
if not val or val.lower() == 'q':
|
|
2948
3084
|
break
|
|
2949
3085
|
try:
|
|
@@ -2989,7 +3125,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2989
3125
|
elif cmd == 'oy':
|
|
2990
3126
|
while True:
|
|
2991
3127
|
cur = ax.get_ylim(); print(f"Current operando Y: {cur[0]:.4g} {cur[1]:.4g}")
|
|
2992
|
-
line =
|
|
3128
|
+
line = _safe_input("New Y range (min max), w=upper only, s=lower only, a=auto (restore original), q=back: ").strip()
|
|
2993
3129
|
if not line or line.lower() == 'q':
|
|
2994
3130
|
break
|
|
2995
3131
|
if line.lower() == 'w':
|
|
@@ -2997,7 +3133,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2997
3133
|
while True:
|
|
2998
3134
|
cur = ax.get_ylim()
|
|
2999
3135
|
print(f"Current operando Y: {cur[0]:.4g} {cur[1]:.4g}")
|
|
3000
|
-
val =
|
|
3136
|
+
val = _safe_input(f"Enter new upper Y limit (current lower: {cur[0]:.4g}, q=back): ").strip()
|
|
3001
3137
|
if not val or val.lower() == 'q':
|
|
3002
3138
|
break
|
|
3003
3139
|
try:
|
|
@@ -3016,7 +3152,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3016
3152
|
while True:
|
|
3017
3153
|
cur = ax.get_ylim()
|
|
3018
3154
|
print(f"Current operando Y: {cur[0]:.4g} {cur[1]:.4g}")
|
|
3019
|
-
val =
|
|
3155
|
+
val = _safe_input(f"Enter new lower Y limit (current upper: {cur[1]:.4g}, q=back): ").strip()
|
|
3020
3156
|
if not val or val.lower() == 'q':
|
|
3021
3157
|
break
|
|
3022
3158
|
try:
|
|
@@ -3119,9 +3255,9 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3119
3255
|
auto_available = False
|
|
3120
3256
|
|
|
3121
3257
|
if auto_available:
|
|
3122
|
-
line =
|
|
3258
|
+
line = _safe_input("New intensity range (min max, w=upper only, s=lower only, a=auto-fit to visible, q=back): ").strip()
|
|
3123
3259
|
else:
|
|
3124
|
-
line =
|
|
3260
|
+
line = _safe_input("New intensity range (min max, w=upper only, s=lower only, q=back): ").strip()
|
|
3125
3261
|
|
|
3126
3262
|
if not line or line.lower() == 'q':
|
|
3127
3263
|
break
|
|
@@ -3135,7 +3271,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3135
3271
|
except Exception:
|
|
3136
3272
|
print("Could not retrieve current color scale range")
|
|
3137
3273
|
break
|
|
3138
|
-
val =
|
|
3274
|
+
val = _safe_input(f"Enter new upper intensity limit (current lower: {cur[0]:.4g}, q=back): ").strip()
|
|
3139
3275
|
if not val or val.lower() == 'q':
|
|
3140
3276
|
break
|
|
3141
3277
|
try:
|
|
@@ -3161,7 +3297,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3161
3297
|
except Exception:
|
|
3162
3298
|
print("Could not retrieve current color scale range")
|
|
3163
3299
|
break
|
|
3164
|
-
val =
|
|
3300
|
+
val = _safe_input(f"Enter new lower intensity limit (current upper: {cur[1]:.4g}, q=back): ").strip()
|
|
3165
3301
|
if not val or val.lower() == 'q':
|
|
3166
3302
|
break
|
|
3167
3303
|
try:
|
|
@@ -3212,7 +3348,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3212
3348
|
while True:
|
|
3213
3349
|
ax_w_in = getattr(ax, '_fixed_ax_w_in', ax_w_in)
|
|
3214
3350
|
print(f"Current operando width: {ax_w_in:.2f} in")
|
|
3215
|
-
val =
|
|
3351
|
+
val = _safe_input("New width (inches, q=back): ").strip()
|
|
3216
3352
|
if not val or val.lower() == 'q':
|
|
3217
3353
|
break
|
|
3218
3354
|
_snapshot("operando-width")
|
|
@@ -3232,7 +3368,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3232
3368
|
while True:
|
|
3233
3369
|
ec_w_in = getattr(ec_ax, '_fixed_ec_w_in', ec_w_in)
|
|
3234
3370
|
print(f"Current EC width: {ec_w_in:.2f} in")
|
|
3235
|
-
val =
|
|
3371
|
+
val = _safe_input("New EC width (inches, q=back): ").strip()
|
|
3236
3372
|
if not val or val.lower() == 'q':
|
|
3237
3373
|
break
|
|
3238
3374
|
_snapshot("ec-width")
|
|
@@ -3275,7 +3411,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3275
3411
|
if optional:
|
|
3276
3412
|
print("\nOther available: " + ", ".join(optional))
|
|
3277
3413
|
print(_colorize_inline_commands("Append _r to reverse (e.g., viridis_r or 1_r). Blank to cancel."))
|
|
3278
|
-
choice =
|
|
3414
|
+
choice = _safe_input(f"Palette name or number (1-{len(rec_palettes)}): ").strip()
|
|
3279
3415
|
if not choice:
|
|
3280
3416
|
print_menu(); continue
|
|
3281
3417
|
try:
|
|
@@ -3524,7 +3660,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3524
3660
|
last_style_path = getattr(fig, '_last_style_export_path', None)
|
|
3525
3661
|
if ec_ax is None:
|
|
3526
3662
|
print("\nNote: Style export (.bps/.bpsg) is only available in dual-pane mode (with EC file).")
|
|
3527
|
-
sub =
|
|
3663
|
+
sub = _safe_input("Style submenu: (q=return, r=refresh): ").strip().lower()
|
|
3528
3664
|
if sub == 'q':
|
|
3529
3665
|
break
|
|
3530
3666
|
if sub == 'r' or sub == '':
|
|
@@ -3534,9 +3670,9 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3534
3670
|
continue
|
|
3535
3671
|
else:
|
|
3536
3672
|
if last_style_path:
|
|
3537
|
-
sub =
|
|
3673
|
+
sub = _safe_input("Style submenu: (e=export, o=overwrite last, q=return, r=refresh): ").strip().lower()
|
|
3538
3674
|
else:
|
|
3539
|
-
sub =
|
|
3675
|
+
sub = _safe_input("Style submenu: (e=export, q=return, r=refresh): ").strip().lower()
|
|
3540
3676
|
if sub == 'q':
|
|
3541
3677
|
break
|
|
3542
3678
|
if sub == 'r' or sub == '':
|
|
@@ -3549,7 +3685,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3549
3685
|
if not os.path.exists(last_style_path):
|
|
3550
3686
|
print(f"Previous export file not found: {last_style_path}")
|
|
3551
3687
|
continue
|
|
3552
|
-
yn =
|
|
3688
|
+
yn = _safe_input(f"Overwrite '{os.path.basename(last_style_path)}'? (y/n): ").strip().lower()
|
|
3553
3689
|
if yn != 'y':
|
|
3554
3690
|
continue
|
|
3555
3691
|
# Determine export type from existing file and rebuild config
|
|
@@ -3570,7 +3706,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3570
3706
|
print("Export options:")
|
|
3571
3707
|
print(" ps = style only (.bps)")
|
|
3572
3708
|
print(" psg = style + geometry (.bpsg)")
|
|
3573
|
-
exp_choice =
|
|
3709
|
+
exp_choice = _safe_input("Export choice (ps/psg, q=cancel): ").strip().lower()
|
|
3574
3710
|
if not exp_choice or exp_choice == 'q':
|
|
3575
3711
|
print("Style export canceled.")
|
|
3576
3712
|
continue
|
|
@@ -3764,7 +3900,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3764
3900
|
else:
|
|
3765
3901
|
print(f" {_i}: {fname}")
|
|
3766
3902
|
|
|
3767
|
-
choice_name =
|
|
3903
|
+
choice_name = _safe_input("Enter new filename or number to overwrite (q=cancel): ").strip()
|
|
3768
3904
|
if not choice_name or choice_name.lower() == 'q':
|
|
3769
3905
|
print("Style export canceled.")
|
|
3770
3906
|
continue
|
|
@@ -3773,7 +3909,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3773
3909
|
_idx = int(choice_name)
|
|
3774
3910
|
if 1 <= _idx <= len(_style_files):
|
|
3775
3911
|
name = _style_files[_idx-1]
|
|
3776
|
-
yn =
|
|
3912
|
+
yn = _safe_input(f"Overwrite '{name}'? (y/n): ").strip().lower()
|
|
3777
3913
|
if yn == 'y':
|
|
3778
3914
|
target = file_list[_idx-1][1] # Full path from list
|
|
3779
3915
|
else:
|
|
@@ -3790,7 +3926,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3790
3926
|
else:
|
|
3791
3927
|
target = get_organized_path(name, 'style', base_path=save_base)
|
|
3792
3928
|
if os.path.exists(target):
|
|
3793
|
-
yn =
|
|
3929
|
+
yn = _safe_input(f"'{os.path.basename(target)}' exists. Overwrite? (y/n): ").strip().lower()
|
|
3794
3930
|
if yn != 'y':
|
|
3795
3931
|
target = None
|
|
3796
3932
|
if target:
|
|
@@ -3921,7 +4057,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3921
4057
|
try:
|
|
3922
4058
|
im.set_cmap(cmap)
|
|
3923
4059
|
if cbar is not None:
|
|
3924
|
-
cbar.
|
|
4060
|
+
_update_custom_colorbar(cbar.ax, im)
|
|
3925
4061
|
except Exception:
|
|
3926
4062
|
pass
|
|
3927
4063
|
|
|
@@ -4488,14 +4624,14 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4488
4624
|
print(" Subscript: H$_2$O → H₂O | Superscript: m$^2$ → m²")
|
|
4489
4625
|
print(" Bullet: $\\bullet$ → • | Greek: $\\alpha$, $\\beta$ | Angstrom: $\\AA$ → Å")
|
|
4490
4626
|
while True:
|
|
4491
|
-
sub =
|
|
4627
|
+
sub = _safe_input("or> ").strip().lower()
|
|
4492
4628
|
if not sub:
|
|
4493
4629
|
continue
|
|
4494
4630
|
if sub == 'q':
|
|
4495
4631
|
break
|
|
4496
4632
|
if sub == 'x':
|
|
4497
4633
|
cur = ax.get_xlabel() or ''
|
|
4498
|
-
lab =
|
|
4634
|
+
lab = _safe_input(f"New operando X label (blank=cancel, current='{cur}'): ")
|
|
4499
4635
|
if lab:
|
|
4500
4636
|
_snapshot("rename-op-x")
|
|
4501
4637
|
try:
|
|
@@ -4508,7 +4644,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4508
4644
|
pass
|
|
4509
4645
|
elif sub == 'y':
|
|
4510
4646
|
cur = ax.get_ylabel() or ''
|
|
4511
|
-
lab =
|
|
4647
|
+
lab = _safe_input(f"New operando Y label (blank=cancel, current='{cur}'): ")
|
|
4512
4648
|
if lab:
|
|
4513
4649
|
_snapshot("rename-op-y")
|
|
4514
4650
|
try:
|
|
@@ -4540,14 +4676,14 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4540
4676
|
print(" Subscript: H$_2$O → H₂O | Superscript: m$^2$ → m²")
|
|
4541
4677
|
print(" Greek: $\\alpha$, $\\beta$ | Angstrom: $\\AA$ → Å")
|
|
4542
4678
|
while True:
|
|
4543
|
-
sub =
|
|
4679
|
+
sub = _safe_input("er> ").strip().lower()
|
|
4544
4680
|
if not sub:
|
|
4545
4681
|
continue
|
|
4546
4682
|
if sub == 'q':
|
|
4547
4683
|
break
|
|
4548
4684
|
if sub == 'x':
|
|
4549
4685
|
cur = ec_ax.get_xlabel() or ''
|
|
4550
|
-
lab =
|
|
4686
|
+
lab = _safe_input(f"New EC X label (blank=cancel, current='{cur}'): ")
|
|
4551
4687
|
if lab:
|
|
4552
4688
|
_snapshot("rename-ec-x")
|
|
4553
4689
|
try:
|
|
@@ -4559,7 +4695,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4559
4695
|
pass
|
|
4560
4696
|
elif sub == 'y':
|
|
4561
4697
|
cur = ec_ax.get_ylabel() or ''
|
|
4562
|
-
lab =
|
|
4698
|
+
lab = _safe_input(f"New EC Y label (blank=cancel, current='{cur}'): ")
|
|
4563
4699
|
if lab:
|
|
4564
4700
|
_snapshot("rename-ec-y")
|
|
4565
4701
|
try:
|
|
@@ -4597,7 +4733,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4597
4733
|
print_menu(); continue
|
|
4598
4734
|
print("EC line submenu: c=color, l=linewidth, q=back")
|
|
4599
4735
|
while True:
|
|
4600
|
-
sub =
|
|
4736
|
+
sub = _safe_input("el> ").strip().lower()
|
|
4601
4737
|
if not sub:
|
|
4602
4738
|
continue
|
|
4603
4739
|
if sub == 'q':
|
|
@@ -4613,7 +4749,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4613
4749
|
else:
|
|
4614
4750
|
print("\nNo saved colors. Type 'u' to manage saved colors.")
|
|
4615
4751
|
print(" (Enter color name/hex, saved color number, or 'u' to manage)")
|
|
4616
|
-
val =
|
|
4752
|
+
val = _safe_input(f"Color (current={cur}, blank=cancel): ").strip()
|
|
4617
4753
|
if not val:
|
|
4618
4754
|
continue
|
|
4619
4755
|
if val.lower() == 'u':
|
|
@@ -4630,7 +4766,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4630
4766
|
print(f"Invalid color: {e}")
|
|
4631
4767
|
elif sub == 'l':
|
|
4632
4768
|
cur = ln.get_linewidth()
|
|
4633
|
-
val =
|
|
4769
|
+
val = _safe_input(f"Line width (current={cur}, blank=cancel): ").strip()
|
|
4634
4770
|
if not val:
|
|
4635
4771
|
continue
|
|
4636
4772
|
_snapshot("ec-line-width")
|
|
@@ -4655,7 +4791,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4655
4791
|
continue
|
|
4656
4792
|
while True:
|
|
4657
4793
|
cur = ec_ax.get_ylim(); print(f"Current EC time range (Y): {cur[0]:.4g} {cur[1]:.4g}")
|
|
4658
|
-
line =
|
|
4794
|
+
line = _safe_input("New time range (min max), w=upper only, s=lower only, a=auto (restore original), q=back: ").strip()
|
|
4659
4795
|
if not line or line.lower() == 'q':
|
|
4660
4796
|
break
|
|
4661
4797
|
if line.lower() == 'w':
|
|
@@ -4663,7 +4799,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4663
4799
|
while True:
|
|
4664
4800
|
cur = ec_ax.get_ylim()
|
|
4665
4801
|
print(f"Current EC time range (Y): {cur[0]:.4g} {cur[1]:.4g}")
|
|
4666
|
-
val =
|
|
4802
|
+
val = _safe_input(f"Enter new upper time limit (current lower: {cur[0]:.4g}, q=back): ").strip()
|
|
4667
4803
|
if not val or val.lower() == 'q':
|
|
4668
4804
|
break
|
|
4669
4805
|
try:
|
|
@@ -4681,7 +4817,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4681
4817
|
while True:
|
|
4682
4818
|
cur = ec_ax.get_ylim()
|
|
4683
4819
|
print(f"Current EC time range (Y): {cur[0]:.4g} {cur[1]:.4g}")
|
|
4684
|
-
val =
|
|
4820
|
+
val = _safe_input(f"Enter new lower time limit (current upper: {cur[1]:.4g}, q=back): ").strip()
|
|
4685
4821
|
if not val or val.lower() == 'q':
|
|
4686
4822
|
break
|
|
4687
4823
|
try:
|
|
@@ -4795,7 +4931,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4795
4931
|
print("The .mpt file must contain the '<I>/mA' column to use this feature.")
|
|
4796
4932
|
print_menu(); continue
|
|
4797
4933
|
while True:
|
|
4798
|
-
sub =
|
|
4934
|
+
sub = _safe_input("ey submenu: n=ions, t=time, q=back: ").strip().lower()
|
|
4799
4935
|
if not sub:
|
|
4800
4936
|
continue
|
|
4801
4937
|
if sub == 'q':
|
|
@@ -4812,7 +4948,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4812
4948
|
prompt = "Enter mass(mg), capacity-per-ion(mAh g^-1), start-ions (e.g. 4.5 26.8 0), q=cancel: "
|
|
4813
4949
|
else:
|
|
4814
4950
|
prompt = f"Enter mass,cap-per-ion,start-ions (blank=reuse {mass_mg} {cap_per_ion} {start_ions}; q=cancel): "
|
|
4815
|
-
s =
|
|
4951
|
+
s = _safe_input(prompt).strip()
|
|
4816
4952
|
if not s:
|
|
4817
4953
|
if need_input:
|
|
4818
4954
|
continue
|
|
@@ -5098,7 +5234,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
5098
5234
|
while True:
|
|
5099
5235
|
cur = ec_ax.get_xlim()
|
|
5100
5236
|
print(f"Current EC X range: {cur[0]:.4g} {cur[1]:.4g}")
|
|
5101
|
-
line =
|
|
5237
|
+
line = _safe_input("New EC X range (min max), w=upper only, s=lower only, a=auto (restore original), q=back: ").strip()
|
|
5102
5238
|
if not line or line.lower() == 'q':
|
|
5103
5239
|
break
|
|
5104
5240
|
if line.lower() == 'w':
|
|
@@ -5106,7 +5242,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
5106
5242
|
while True:
|
|
5107
5243
|
cur = ec_ax.get_xlim()
|
|
5108
5244
|
print(f"Current EC X range: {cur[0]:.4g} {cur[1]:.4g}")
|
|
5109
|
-
val =
|
|
5245
|
+
val = _safe_input(f"Enter new upper EC X limit (current lower: {cur[0]:.4g}, q=back): ").strip()
|
|
5110
5246
|
if not val or val.lower() == 'q':
|
|
5111
5247
|
break
|
|
5112
5248
|
try:
|
|
@@ -5125,7 +5261,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
5125
5261
|
while True:
|
|
5126
5262
|
cur = ec_ax.get_xlim()
|
|
5127
5263
|
print(f"Current EC X range: {cur[0]:.4g} {cur[1]:.4g}")
|
|
5128
|
-
val =
|
|
5264
|
+
val = _safe_input(f"Enter new lower EC X limit (current upper: {cur[1]:.4g}, q=back): ").strip()
|
|
5129
5265
|
if not val or val.lower() == 'q':
|
|
5130
5266
|
break
|
|
5131
5267
|
try:
|
|
@@ -5187,7 +5323,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
5187
5323
|
cur_w, cur_h = _get_fig_size(fig)
|
|
5188
5324
|
print(f"Current canvas size: {cur_w:.2f} x {cur_h:.2f} in (W x H)")
|
|
5189
5325
|
print("Canvas: only figure size will change; panel widths/gaps are not altered.")
|
|
5190
|
-
line =
|
|
5326
|
+
line = _safe_input("New canvas size 'W H' (blank=cancel): ").strip()
|
|
5191
5327
|
if line:
|
|
5192
5328
|
_snapshot("canvas-size")
|
|
5193
5329
|
try:
|