batplot 1.1.2__tar.gz → 1.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {batplot-1.1.2 → batplot-1.1.4}/PKG-INFO +1 -1
- {batplot-1.1.2 → batplot-1.1.4}/batplot/batplot.py +71 -23
- {batplot-1.1.2 → batplot-1.1.4}/batplot/electrochem_interactive.py +47 -30
- {batplot-1.1.2 → batplot-1.1.4}/batplot/interactive.py +4 -31
- {batplot-1.1.2 → batplot-1.1.4}/batplot/modes.py +82 -11
- {batplot-1.1.2 → batplot-1.1.4}/batplot/session.py +10 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot.egg-info/PKG-INFO +1 -1
- {batplot-1.1.2 → batplot-1.1.4}/pyproject.toml +1 -1
- {batplot-1.1.2 → batplot-1.1.4}/LICENSE +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/README.md +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/__init__.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/args.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/batch.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/batplot_new.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/cif.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/cli.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/converters.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/cpc_interactive.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/operando.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/operando_ec_interactive.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/plotting.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/readers.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/style.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/ui.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot/utils.py +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot.egg-info/SOURCES.txt +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot.egg-info/dependency_links.txt +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot.egg-info/entry_points.txt +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot.egg-info/requires.txt +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/batplot.egg-info/top_level.txt +0 -0
- {batplot-1.1.2 → batplot-1.1.4}/setup.cfg +0 -0
|
@@ -536,7 +536,9 @@ def batplot_main() -> int:
|
|
|
536
536
|
_backend = _plt.get_backend()
|
|
537
537
|
except Exception:
|
|
538
538
|
_backend = "unknown"
|
|
539
|
-
|
|
539
|
+
# TkAgg, QtAgg, Qt5Agg, WXAgg, MacOSX etc. are interactive
|
|
540
|
+
_interactive_backends = {"tkagg", "qt5agg", "qt4agg", "qtagg", "wxagg", "macosx", "gtk3agg", "gtk4agg", "wx", "qt", "gtk", "gtk3", "gtk4"}
|
|
541
|
+
_is_noninteractive = isinstance(_backend, str) and (_backend.lower() not in _interactive_backends) and ("agg" in _backend.lower() or _backend.lower() in {"pdf","ps","svg","template"})
|
|
540
542
|
if _is_noninteractive:
|
|
541
543
|
print(f"Matplotlib backend '{_backend}' is non-interactive; a window cannot be shown.")
|
|
542
544
|
print("Tips: unset MPLBACKEND or set a GUI backend, e.g. on macOS:")
|
|
@@ -564,7 +566,10 @@ def batplot_main() -> int:
|
|
|
564
566
|
_backend = _plt.get_backend()
|
|
565
567
|
except Exception:
|
|
566
568
|
_backend = "unknown"
|
|
567
|
-
|
|
569
|
+
# TkAgg, QtAgg, Qt5Agg, WXAgg, MacOSX etc. are interactive
|
|
570
|
+
_interactive_backends = {"tkagg", "qt5agg", "qt4agg", "qtagg", "wxagg", "macosx", "gtk3agg", "gtk4agg", "wx", "qt", "gtk", "gtk3", "gtk4"}
|
|
571
|
+
_is_noninteractive = isinstance(_backend, str) and (_backend.lower() not in _interactive_backends) and ("agg" in _backend.lower() or _backend.lower() in {"pdf","ps","svg","template"})
|
|
572
|
+
if not _is_noninteractive:
|
|
568
573
|
_plt.show()
|
|
569
574
|
else:
|
|
570
575
|
print(f"Matplotlib backend '{_backend}' is non-interactive; use --out to save the figure.")
|
|
@@ -731,7 +736,9 @@ def batplot_main() -> int:
|
|
|
731
736
|
_backend = plt.get_backend()
|
|
732
737
|
except Exception:
|
|
733
738
|
_backend = "unknown"
|
|
734
|
-
|
|
739
|
+
# TkAgg, QtAgg, Qt5Agg, WXAgg, MacOSX etc. are interactive
|
|
740
|
+
_interactive_backends = {"tkagg", "qt5agg", "qt4agg", "qtagg", "wxagg", "macosx", "gtk3agg", "gtk4agg", "wx", "qt", "gtk", "gtk3", "gtk4"}
|
|
741
|
+
_is_noninteractive = isinstance(_backend, str) and (_backend.lower() not in _interactive_backends) and ("agg" in _backend.lower() or _backend.lower() in {"pdf","ps","svg","template"})
|
|
735
742
|
if _is_noninteractive:
|
|
736
743
|
print(f"Matplotlib backend '{_backend}' is non-interactive; a window cannot be shown.")
|
|
737
744
|
print("Tips: unset MPLBACKEND or set a GUI backend, e.g. on macOS:")
|
|
@@ -765,7 +772,18 @@ def batplot_main() -> int:
|
|
|
765
772
|
# Keep window open after menu
|
|
766
773
|
plt.show()
|
|
767
774
|
else:
|
|
768
|
-
|
|
775
|
+
if not (args.savefig or args.out):
|
|
776
|
+
try:
|
|
777
|
+
_backend = plt.get_backend()
|
|
778
|
+
except Exception:
|
|
779
|
+
_backend = "unknown"
|
|
780
|
+
# TkAgg, QtAgg, Qt5Agg, WXAgg, MacOSX etc. are interactive
|
|
781
|
+
_interactive_backends = {"tkagg", "qt5agg", "qt4agg", "qtagg", "wxagg", "macosx", "gtk3agg", "gtk4agg", "wx", "qt", "gtk", "gtk3", "gtk4"}
|
|
782
|
+
_is_noninteractive = isinstance(_backend, str) and (_backend.lower() not in _interactive_backends) and ("agg" in _backend.lower() or _backend.lower() in {"pdf","ps","svg","template"})
|
|
783
|
+
if not _is_noninteractive:
|
|
784
|
+
plt.show()
|
|
785
|
+
else:
|
|
786
|
+
print(f"Matplotlib backend '{_backend}' is non-interactive; use --out to save the figure.")
|
|
769
787
|
exit(0)
|
|
770
788
|
|
|
771
789
|
# dQ/dV plotting mode for supported .csv electrochemistry exports
|
|
@@ -936,7 +954,9 @@ def batplot_main() -> int:
|
|
|
936
954
|
_backend = _plt.get_backend()
|
|
937
955
|
except Exception:
|
|
938
956
|
_backend = "unknown"
|
|
939
|
-
|
|
957
|
+
# TkAgg, QtAgg, Qt5Agg, WXAgg, MacOSX etc. are interactive
|
|
958
|
+
_interactive_backends = {"tkagg", "qt5agg", "qt4agg", "qtagg", "wxagg", "macosx", "gtk3agg", "gtk4agg", "wx", "qt", "gtk", "gtk3", "gtk4"}
|
|
959
|
+
_is_noninteractive = isinstance(_backend, str) and (_backend.lower() not in _interactive_backends) and ("agg" in _backend.lower() or _backend.lower() in {"pdf","ps","svg","template"})
|
|
940
960
|
if _is_noninteractive:
|
|
941
961
|
print(f"Matplotlib backend '{_backend}' is non-interactive; a window cannot be shown.")
|
|
942
962
|
print("Tips: unset MPLBACKEND or set a GUI backend, e.g. on macOS:")
|
|
@@ -961,7 +981,10 @@ def batplot_main() -> int:
|
|
|
961
981
|
_backend = _plt.get_backend()
|
|
962
982
|
except Exception:
|
|
963
983
|
_backend = "unknown"
|
|
964
|
-
|
|
984
|
+
# TkAgg, QtAgg, Qt5Agg, WXAgg, MacOSX etc. are interactive
|
|
985
|
+
_interactive_backends = {"tkagg", "qt5agg", "qt4agg", "qtagg", "wxagg", "macosx", "gtk3agg", "gtk4agg", "wx", "qt", "gtk", "gtk3", "gtk4"}
|
|
986
|
+
_is_noninteractive = isinstance(_backend, str) and (_backend.lower() not in _interactive_backends) and ("agg" in _backend.lower() or _backend.lower() in {"pdf","ps","svg","template"})
|
|
987
|
+
if not _is_noninteractive:
|
|
965
988
|
_plt.show()
|
|
966
989
|
else:
|
|
967
990
|
print(f"Matplotlib backend '{_backend}' is non-interactive; use --out to save the figure.")
|
|
@@ -1039,29 +1062,44 @@ def batplot_main() -> int:
|
|
|
1039
1062
|
# Interactive or show
|
|
1040
1063
|
if args.interactive:
|
|
1041
1064
|
try:
|
|
1042
|
-
_plt.
|
|
1043
|
-
except Exception:
|
|
1044
|
-
pass
|
|
1045
|
-
try:
|
|
1046
|
-
_plt.show(block=False)
|
|
1065
|
+
_backend = _plt.get_backend()
|
|
1047
1066
|
except Exception:
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1067
|
+
_backend = "unknown"
|
|
1068
|
+
# TkAgg, QtAgg, Qt5Agg, WXAgg, MacOSX etc. are interactive
|
|
1069
|
+
_interactive_backends = {"tkagg", "qt5agg", "qt4agg", "qtagg", "wxagg", "macosx", "gtk3agg", "gtk4agg", "wx", "qt", "gtk", "gtk3", "gtk4"}
|
|
1070
|
+
_is_noninteractive = isinstance(_backend, str) and (_backend.lower() not in _interactive_backends) and ("agg" in _backend.lower() or _backend.lower() in {"pdf","ps","svg","template"})
|
|
1071
|
+
if _is_noninteractive:
|
|
1072
|
+
print(f"Matplotlib backend '{_backend}' is non-interactive; a window cannot be shown.")
|
|
1073
|
+
print("Tips: unset MPLBACKEND or set a GUI backend")
|
|
1074
|
+
print("Or run without --interactive and use --out to save the figure.")
|
|
1075
|
+
else:
|
|
1076
|
+
try:
|
|
1077
|
+
_plt.ion()
|
|
1078
|
+
except Exception:
|
|
1079
|
+
pass
|
|
1080
|
+
try:
|
|
1081
|
+
_plt.show(block=False)
|
|
1082
|
+
except Exception:
|
|
1083
|
+
pass
|
|
1084
|
+
try:
|
|
1085
|
+
if has_ec and (operando_ec_interactive_menu is not None) and (ec_ax is not None):
|
|
1086
|
+
operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax)
|
|
1087
|
+
else:
|
|
1088
|
+
# Operando-only interactive menu has been removed; fall back to non-interactive view
|
|
1089
|
+
print("Operando-only interactive menu is no longer available; showing figure without interactive controls.\nTip: include EC data to use the combined operando+EC interactive menu.")
|
|
1090
|
+
except Exception as _ie:
|
|
1091
|
+
print(f"Interactive menu failed: {_ie}")
|
|
1092
|
+
_plt.show()
|
|
1058
1093
|
else:
|
|
1059
1094
|
if not (args.savefig or args.out):
|
|
1060
1095
|
try:
|
|
1061
1096
|
_backend = _plt.get_backend()
|
|
1062
1097
|
except Exception:
|
|
1063
1098
|
_backend = "unknown"
|
|
1064
|
-
|
|
1099
|
+
# TkAgg, QtAgg, Qt5Agg, WXAgg, MacOSX etc. are interactive
|
|
1100
|
+
_interactive_backends = {"tkagg", "qt5agg", "qt4agg", "qtagg", "wxagg", "macosx", "gtk3agg", "gtk4agg", "wx", "qt", "gtk", "gtk3", "gtk4"}
|
|
1101
|
+
_is_noninteractive = isinstance(_backend, str) and (_backend.lower() not in _interactive_backends) and ("agg" in _backend.lower() or _backend.lower() in {"pdf","ps","svg","template"})
|
|
1102
|
+
if not _is_noninteractive:
|
|
1065
1103
|
_plt.show()
|
|
1066
1104
|
else:
|
|
1067
1105
|
print(f"Matplotlib backend '{_backend}' is non-interactive; use --out to save the figure.")
|
|
@@ -2324,7 +2362,17 @@ def batplot_main() -> int:
|
|
|
2324
2362
|
print(f"Saved plot to {export_target}")
|
|
2325
2363
|
else:
|
|
2326
2364
|
# Default: show the plot in non-interactive, non-save mode
|
|
2327
|
-
|
|
2365
|
+
try:
|
|
2366
|
+
_backend = plt.get_backend()
|
|
2367
|
+
except Exception:
|
|
2368
|
+
_backend = "unknown"
|
|
2369
|
+
# TkAgg, QtAgg, Qt5Agg, WXAgg, MacOSX etc. are interactive
|
|
2370
|
+
_interactive_backends = {"tkagg", "qt5agg", "qt4agg", "qtagg", "wxagg", "macosx", "gtk3agg", "gtk4agg", "wx", "qt", "gtk", "gtk3", "gtk4"}
|
|
2371
|
+
_is_noninteractive = isinstance(_backend, str) and (_backend.lower() not in _interactive_backends) and ("agg" in _backend.lower() or _backend.lower() in {"pdf","ps","svg","template"})
|
|
2372
|
+
if not _is_noninteractive:
|
|
2373
|
+
plt.show()
|
|
2374
|
+
else:
|
|
2375
|
+
print(f"Matplotlib backend '{_backend}' is non-interactive; use --out to save the figure.")
|
|
2328
2376
|
|
|
2329
2377
|
# Success
|
|
2330
2378
|
return 0
|
|
@@ -25,7 +25,7 @@ from .plotting import update_labels as _update_labels
|
|
|
25
25
|
from .utils import _confirm_overwrite
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def _print_menu(n_cycles: int):
|
|
28
|
+
def _print_menu(n_cycles: int, is_dqdv: bool = False):
|
|
29
29
|
# Three-column menu similar to operando: Styles | Geometries | Options
|
|
30
30
|
# Use dynamic column widths for clean alignment.
|
|
31
31
|
col1 = [
|
|
@@ -38,11 +38,14 @@ def _print_menu(n_cycles: int):
|
|
|
38
38
|
]
|
|
39
39
|
col2 = [
|
|
40
40
|
"c: cycles/colors",
|
|
41
|
-
"a: capacity/ion",
|
|
42
41
|
"r: rename axes",
|
|
43
42
|
"x: x-scale",
|
|
44
43
|
"y: y-scale",
|
|
45
44
|
]
|
|
45
|
+
# Only show capacity/ion option when NOT in dQdV mode
|
|
46
|
+
if not is_dqdv:
|
|
47
|
+
col2.insert(1, "a: capacity/ion")
|
|
48
|
+
|
|
46
49
|
col3 = [
|
|
47
50
|
"p: print(export) style",
|
|
48
51
|
"i: import style",
|
|
@@ -364,6 +367,15 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
364
367
|
|
|
365
368
|
base_xlabel = ax.get_xlabel() or ''
|
|
366
369
|
base_ylabel = ax.get_ylabel() or ''
|
|
370
|
+
|
|
371
|
+
# Detect dQdV mode: check stored flag first, then fall back to y-label detection
|
|
372
|
+
# This handles cases where the user renamed the y-axis and saved/reloaded the session
|
|
373
|
+
is_dqdv = getattr(ax, '_is_dqdv_mode', None)
|
|
374
|
+
if is_dqdv is None:
|
|
375
|
+
# Initial detection: check if y-label contains "dQ"
|
|
376
|
+
is_dqdv = 'dQ' in base_ylabel
|
|
377
|
+
# Store the mode on the axes for persistence
|
|
378
|
+
ax._is_dqdv_mode = is_dqdv
|
|
367
379
|
|
|
368
380
|
def _set_spine_visible(which: str, visible: bool):
|
|
369
381
|
sp = ax.spines.get(which)
|
|
@@ -765,7 +777,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
765
777
|
print("Undo: restored previous state.")
|
|
766
778
|
except Exception as e:
|
|
767
779
|
print(f"Undo failed: {e}")
|
|
768
|
-
_print_menu(len(all_cycles))
|
|
780
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
769
781
|
while True:
|
|
770
782
|
key = input("Press a key: ").strip().lower()
|
|
771
783
|
if not key:
|
|
@@ -778,18 +790,18 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
778
790
|
if confirm == 'y':
|
|
779
791
|
break
|
|
780
792
|
else:
|
|
781
|
-
_print_menu(len(all_cycles))
|
|
793
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
782
794
|
continue
|
|
783
795
|
elif key == 'b':
|
|
784
796
|
restore_state()
|
|
785
|
-
_print_menu(len(all_cycles))
|
|
797
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
786
798
|
continue
|
|
787
799
|
elif key == 'e':
|
|
788
800
|
# Export current figure to a file; default extension .svg if missing
|
|
789
801
|
try:
|
|
790
802
|
fname = input("Export filename (default .svg if no extension, q=cancel): ").strip()
|
|
791
803
|
if not fname or fname.lower() == 'q':
|
|
792
|
-
_print_menu(len(all_cycles))
|
|
804
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
793
805
|
continue
|
|
794
806
|
root, ext = os.path.splitext(fname)
|
|
795
807
|
if ext == '':
|
|
@@ -843,7 +855,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
843
855
|
print(f"Export failed: {e}")
|
|
844
856
|
except Exception as e:
|
|
845
857
|
print(f"Error exporting figure: {e}")
|
|
846
|
-
_print_menu(len(all_cycles))
|
|
858
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
847
859
|
continue
|
|
848
860
|
elif key == 'h':
|
|
849
861
|
# Legend submenu: toggle visibility and move legend in inches relative to canvas center
|
|
@@ -948,7 +960,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
948
960
|
print("Unknown option.")
|
|
949
961
|
except Exception:
|
|
950
962
|
pass
|
|
951
|
-
_print_menu(len(all_cycles))
|
|
963
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
952
964
|
continue
|
|
953
965
|
elif key == 'p':
|
|
954
966
|
# Print current style and optionally export to .bpcfg
|
|
@@ -964,7 +976,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
964
976
|
|
|
965
977
|
except Exception as e:
|
|
966
978
|
print(f"Error in style menu: {e}")
|
|
967
|
-
_print_menu(len(all_cycles))
|
|
979
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
968
980
|
continue
|
|
969
981
|
elif key == 'i':
|
|
970
982
|
# Import style from .bpcfg (with numbered list)
|
|
@@ -980,13 +992,13 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
980
992
|
print(f" {_i}: {_f}")
|
|
981
993
|
inp = input("Enter number to open or filename (.bpcfg, q=cancel): ").strip()
|
|
982
994
|
if not inp or inp.lower() == 'q':
|
|
983
|
-
_print_menu(len(all_cycles)); continue
|
|
995
|
+
_print_menu(len(all_cycles), is_dqdv); continue
|
|
984
996
|
if inp.isdigit() and _bpcfg_files:
|
|
985
997
|
_idx = int(inp)
|
|
986
998
|
if 1 <= _idx <= len(_bpcfg_files):
|
|
987
999
|
path = os.path.join(os.getcwd(), _bpcfg_files[_idx-1])
|
|
988
1000
|
else:
|
|
989
|
-
print("Invalid number."); _print_menu(len(all_cycles)); continue
|
|
1001
|
+
print("Invalid number."); _print_menu(len(all_cycles), is_dqdv); continue
|
|
990
1002
|
else:
|
|
991
1003
|
path = inp
|
|
992
1004
|
if not os.path.isfile(path):
|
|
@@ -996,14 +1008,14 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
996
1008
|
if os.path.isfile(alt):
|
|
997
1009
|
path = alt
|
|
998
1010
|
else:
|
|
999
|
-
print("File not found."); _print_menu(len(all_cycles)); continue
|
|
1011
|
+
print("File not found."); _print_menu(len(all_cycles), is_dqdv); continue
|
|
1000
1012
|
else:
|
|
1001
|
-
print("File not found."); _print_menu(len(all_cycles)); continue
|
|
1013
|
+
print("File not found."); _print_menu(len(all_cycles), is_dqdv); continue
|
|
1002
1014
|
with open(path, 'r', encoding='utf-8') as f:
|
|
1003
1015
|
cfg = json.load(f)
|
|
1004
1016
|
if not isinstance(cfg, dict) or cfg.get('kind') != 'ec_style':
|
|
1005
1017
|
print("Not an EC style file.")
|
|
1006
|
-
_print_menu(len(all_cycles))
|
|
1018
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
1007
1019
|
continue
|
|
1008
1020
|
|
|
1009
1021
|
# --- Apply comprehensive style (no curve data) ---
|
|
@@ -1087,7 +1099,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1087
1099
|
|
|
1088
1100
|
except Exception as e:
|
|
1089
1101
|
print(f"Error importing style: {e}")
|
|
1090
|
-
_print_menu(len(all_cycles))
|
|
1102
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
1091
1103
|
continue
|
|
1092
1104
|
elif key == 'l':
|
|
1093
1105
|
# Line widths submenu: curves vs frame/ticks
|
|
@@ -1262,7 +1274,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1262
1274
|
print("Unknown option.")
|
|
1263
1275
|
except Exception as e:
|
|
1264
1276
|
print(f"Error in line submenu: {e}")
|
|
1265
|
-
_print_menu(len(all_cycles))
|
|
1277
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
1266
1278
|
continue
|
|
1267
1279
|
elif key == 'r':
|
|
1268
1280
|
# Rename axis labels
|
|
@@ -1323,7 +1335,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1323
1335
|
fig.canvas.draw_idle()
|
|
1324
1336
|
except Exception as e:
|
|
1325
1337
|
print(f"Error renaming axes: {e}")
|
|
1326
|
-
_print_menu(len(all_cycles))
|
|
1338
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
1327
1339
|
continue
|
|
1328
1340
|
elif key == 't':
|
|
1329
1341
|
# Unified WASD: w/a/s/d x 1..5 => spine, ticks, minor, labels, title
|
|
@@ -1442,7 +1454,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1442
1454
|
fig.canvas.draw_idle()
|
|
1443
1455
|
except Exception as e:
|
|
1444
1456
|
print(f"Error in WASD tick visibility menu: {e}")
|
|
1445
|
-
_print_menu(len(all_cycles))
|
|
1457
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
1446
1458
|
continue
|
|
1447
1459
|
elif key == 's':
|
|
1448
1460
|
try:
|
|
@@ -1458,18 +1470,18 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1458
1470
|
print(f" {i}: {f}")
|
|
1459
1471
|
choice = input("Enter new filename or number to overwrite (q=cancel): ").strip()
|
|
1460
1472
|
if not choice or choice.lower() == 'q':
|
|
1461
|
-
_print_menu(len(all_cycles)); continue
|
|
1473
|
+
_print_menu(len(all_cycles), is_dqdv); continue
|
|
1462
1474
|
if choice.isdigit() and files:
|
|
1463
1475
|
idx = int(choice)
|
|
1464
1476
|
if 1 <= idx <= len(files):
|
|
1465
1477
|
name = files[idx-1]
|
|
1466
1478
|
yn = input(f"Overwrite '{name}'? (y/n): ").strip().lower()
|
|
1467
1479
|
if yn != 'y':
|
|
1468
|
-
_print_menu(len(all_cycles)); continue
|
|
1480
|
+
_print_menu(len(all_cycles), is_dqdv); continue
|
|
1469
1481
|
target = os.path.join(folder, name)
|
|
1470
1482
|
else:
|
|
1471
1483
|
print("Invalid number.")
|
|
1472
|
-
_print_menu(len(all_cycles)); continue
|
|
1484
|
+
_print_menu(len(all_cycles), is_dqdv); continue
|
|
1473
1485
|
else:
|
|
1474
1486
|
name = choice
|
|
1475
1487
|
root, ext = os.path.splitext(name)
|
|
@@ -1479,11 +1491,11 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1479
1491
|
if os.path.exists(target):
|
|
1480
1492
|
yn = input(f"'{os.path.basename(target)}' exists. Overwrite? (y/n): ").strip().lower()
|
|
1481
1493
|
if yn != 'y':
|
|
1482
|
-
_print_menu(len(all_cycles)); continue
|
|
1494
|
+
_print_menu(len(all_cycles), is_dqdv); continue
|
|
1483
1495
|
dump_ec_session(target, fig=fig, ax=ax, cycle_lines=cycle_lines, skip_confirm=True)
|
|
1484
1496
|
except Exception as e:
|
|
1485
1497
|
print(f"Save failed: {e}")
|
|
1486
|
-
_print_menu(len(all_cycles))
|
|
1498
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
1487
1499
|
continue
|
|
1488
1500
|
elif key == 'c':
|
|
1489
1501
|
print(f"Cycles present ({len(all_cycles)} total):", ", ".join(str(c) for c in all_cycles))
|
|
@@ -1559,9 +1571,14 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1559
1571
|
if ignored:
|
|
1560
1572
|
print("Ignored cycles:", ", ".join(str(c) for c in ignored))
|
|
1561
1573
|
# Show the menu again after completing the command
|
|
1562
|
-
_print_menu(len(all_cycles))
|
|
1574
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
1563
1575
|
continue
|
|
1564
1576
|
elif key == 'a':
|
|
1577
|
+
# X-axis submenu: number-of-ions vs capacity (not available in dQdV mode)
|
|
1578
|
+
if is_dqdv:
|
|
1579
|
+
print("Capacity/ion conversion is not available in dQ/dV mode.")
|
|
1580
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
1581
|
+
continue
|
|
1565
1582
|
# X-axis submenu: number-of-ions vs capacity
|
|
1566
1583
|
while True:
|
|
1567
1584
|
print("X-axis menu: n=number of ions, c=capacity, q=back")
|
|
@@ -1625,7 +1642,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1625
1642
|
fig.canvas.draw()
|
|
1626
1643
|
except Exception:
|
|
1627
1644
|
fig.canvas.draw_idle()
|
|
1628
|
-
_print_menu(len(all_cycles))
|
|
1645
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
1629
1646
|
continue
|
|
1630
1647
|
elif key == 'f':
|
|
1631
1648
|
# Font submenu with numbered options
|
|
@@ -1694,7 +1711,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1694
1711
|
print("Size must be positive.")
|
|
1695
1712
|
except Exception:
|
|
1696
1713
|
print("Invalid size.")
|
|
1697
|
-
_print_menu(len(all_cycles))
|
|
1714
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
1698
1715
|
continue
|
|
1699
1716
|
elif key == 'x':
|
|
1700
1717
|
# X-axis: set limits only
|
|
@@ -1708,7 +1725,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1708
1725
|
fig.canvas.draw()
|
|
1709
1726
|
except Exception:
|
|
1710
1727
|
print("Invalid limits, ignored.")
|
|
1711
|
-
_print_menu(len(all_cycles))
|
|
1728
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
1712
1729
|
continue
|
|
1713
1730
|
elif key == 'y':
|
|
1714
1731
|
# Y-axis: set limits only
|
|
@@ -1722,7 +1739,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1722
1739
|
fig.canvas.draw()
|
|
1723
1740
|
except Exception:
|
|
1724
1741
|
print("Invalid limits, ignored.")
|
|
1725
|
-
_print_menu(len(all_cycles))
|
|
1742
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
1726
1743
|
continue
|
|
1727
1744
|
elif key == 'g':
|
|
1728
1745
|
# Geometry submenu: plot frame vs canvas (scales moved to separate keys)
|
|
@@ -1751,11 +1768,11 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1751
1768
|
fig.canvas.draw()
|
|
1752
1769
|
except Exception:
|
|
1753
1770
|
fig.canvas.draw_idle()
|
|
1754
|
-
_print_menu(len(all_cycles))
|
|
1771
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
1755
1772
|
continue
|
|
1756
1773
|
else:
|
|
1757
1774
|
print("Unknown command.")
|
|
1758
|
-
_print_menu(len(all_cycles))
|
|
1775
|
+
_print_menu(len(all_cycles), is_dqdv)
|
|
1759
1776
|
|
|
1760
1777
|
|
|
1761
1778
|
def _get_style_snapshot(fig, ax, cycle_lines: Dict, tick_state: Dict) -> Dict:
|
|
@@ -1969,37 +1969,10 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1969
1969
|
if sub == 'r' or sub == '':
|
|
1970
1970
|
continue
|
|
1971
1971
|
if sub == 'e':
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
target = None
|
|
1977
|
-
if choice.isdigit() and _bpcfg_files:
|
|
1978
|
-
_idx = int(choice)
|
|
1979
|
-
if 1 <= _idx <= len(_bpcfg_files):
|
|
1980
|
-
name = _bpcfg_files[_idx-1]
|
|
1981
|
-
yn = input(f"Overwrite '{name}'? (y/n): ").strip().lower()
|
|
1982
|
-
if yn == 'y':
|
|
1983
|
-
target = os.path.join(os.getcwd(), name)
|
|
1984
|
-
else:
|
|
1985
|
-
print("Invalid number.")
|
|
1986
|
-
else:
|
|
1987
|
-
name = choice
|
|
1988
|
-
root, ext = os.path.splitext(name)
|
|
1989
|
-
if ext == '':
|
|
1990
|
-
name = name + '.bpcfg'
|
|
1991
|
-
target = name if os.path.isabs(name) else os.path.join(os.getcwd(), name)
|
|
1992
|
-
if os.path.exists(target):
|
|
1993
|
-
yn = input(f"'{os.path.basename(target)}' exists. Overwrite? (y/n): ").strip().lower()
|
|
1994
|
-
if yn != 'y':
|
|
1995
|
-
target = None
|
|
1996
|
-
if target:
|
|
1997
|
-
export_style_config(target)
|
|
1998
|
-
print(f"Exported style to {target}")
|
|
1999
|
-
style_menu_active = False # Exit style submenu and return to main menu
|
|
2000
|
-
break
|
|
2001
|
-
else:
|
|
2002
|
-
print("Export canceled.")
|
|
1972
|
+
# Call export_style_config which handles the entire export dialog
|
|
1973
|
+
export_style_config(None) # The filename parameter is ignored by the function
|
|
1974
|
+
style_menu_active = False # Exit style submenu and return to main menu
|
|
1975
|
+
break
|
|
2003
1976
|
else:
|
|
2004
1977
|
print("Unknown choice.")
|
|
2005
1978
|
except Exception as e:
|
|
@@ -111,20 +111,86 @@ def handle_cv_mode(args) -> int:
|
|
|
111
111
|
ax.legend()
|
|
112
112
|
fig.subplots_adjust(left=0.12, right=0.95, top=0.88, bottom=0.15)
|
|
113
113
|
|
|
114
|
+
# Save if requested
|
|
115
|
+
outname = args.savefig or args.out
|
|
116
|
+
if outname:
|
|
117
|
+
if not os.path.splitext(outname)[1]:
|
|
118
|
+
outname += '.svg'
|
|
119
|
+
_, _ext = os.path.splitext(outname)
|
|
120
|
+
if _ext.lower() == '.svg':
|
|
121
|
+
try:
|
|
122
|
+
_fig_fc = fig.get_facecolor()
|
|
123
|
+
except Exception:
|
|
124
|
+
_fig_fc = None
|
|
125
|
+
try:
|
|
126
|
+
_ax_fc = ax.get_facecolor()
|
|
127
|
+
except Exception:
|
|
128
|
+
_ax_fc = None
|
|
129
|
+
try:
|
|
130
|
+
if getattr(fig, 'patch', None) is not None:
|
|
131
|
+
fig.patch.set_alpha(0.0)
|
|
132
|
+
fig.patch.set_facecolor('none')
|
|
133
|
+
if getattr(ax, 'patch', None) is not None:
|
|
134
|
+
ax.patch.set_alpha(0.0)
|
|
135
|
+
ax.patch.set_facecolor('none')
|
|
136
|
+
except Exception:
|
|
137
|
+
pass
|
|
138
|
+
try:
|
|
139
|
+
fig.savefig(outname, dpi=300, transparent=True, facecolor='none', edgecolor='none')
|
|
140
|
+
finally:
|
|
141
|
+
try:
|
|
142
|
+
if _fig_fc is not None and getattr(fig, 'patch', None) is not None:
|
|
143
|
+
fig.patch.set_alpha(1.0)
|
|
144
|
+
fig.patch.set_facecolor(_fig_fc)
|
|
145
|
+
except Exception:
|
|
146
|
+
pass
|
|
147
|
+
try:
|
|
148
|
+
if _ax_fc is not None and getattr(ax, 'patch', None) is not None:
|
|
149
|
+
ax.patch.set_alpha(1.0)
|
|
150
|
+
ax.patch.set_facecolor(_ax_fc)
|
|
151
|
+
except Exception:
|
|
152
|
+
pass
|
|
153
|
+
else:
|
|
154
|
+
fig.savefig(outname, dpi=300)
|
|
155
|
+
print(f"CV plot saved to {outname}")
|
|
156
|
+
|
|
114
157
|
# Interactive menu
|
|
115
158
|
if args.interactive:
|
|
116
159
|
try:
|
|
117
|
-
plt.
|
|
160
|
+
_backend = plt.get_backend()
|
|
118
161
|
except Exception:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
print(f"
|
|
125
|
-
|
|
162
|
+
_backend = "unknown"
|
|
163
|
+
# TkAgg, QtAgg, Qt5Agg, WXAgg, MacOSX etc. are interactive
|
|
164
|
+
_interactive_backends = {"tkagg", "qt5agg", "qt4agg", "qtagg", "wxagg", "macosx", "gtk3agg", "gtk4agg", "wx", "qt", "gtk", "gtk3", "gtk4"}
|
|
165
|
+
_is_noninteractive = isinstance(_backend, str) and (_backend.lower() not in _interactive_backends) and ("agg" in _backend.lower() or _backend.lower() in {"pdf","ps","svg","template"})
|
|
166
|
+
if _is_noninteractive:
|
|
167
|
+
print(f"Matplotlib backend '{_backend}' is non-interactive; a window cannot be shown.")
|
|
168
|
+
print("Tips: unset MPLBACKEND or set a GUI backend")
|
|
169
|
+
print("Or run without --interactive and use --out to save the figure.")
|
|
170
|
+
else:
|
|
171
|
+
try:
|
|
172
|
+
plt.ion()
|
|
173
|
+
except Exception:
|
|
174
|
+
pass
|
|
175
|
+
plt.show(block=False)
|
|
176
|
+
try:
|
|
177
|
+
electrochem_interactive_menu(fig, ax, cycle_lines)
|
|
178
|
+
except Exception as _ie:
|
|
179
|
+
print(f"Interactive menu failed: {_ie}")
|
|
180
|
+
plt.show()
|
|
126
181
|
else:
|
|
127
|
-
|
|
182
|
+
if not (args.savefig or args.out):
|
|
183
|
+
try:
|
|
184
|
+
_backend = plt.get_backend()
|
|
185
|
+
except Exception:
|
|
186
|
+
_backend = "unknown"
|
|
187
|
+
# TkAgg, QtAgg, Qt5Agg, WXAgg, MacOSX etc. are interactive
|
|
188
|
+
_interactive_backends = {"tkagg", "qt5agg", "qt4agg", "qtagg", "wxagg", "macosx", "gtk3agg", "gtk4agg", "wx", "qt", "gtk", "gtk3", "gtk4"}
|
|
189
|
+
_is_noninteractive = isinstance(_backend, str) and (_backend.lower() not in _interactive_backends) and ("agg" in _backend.lower() or _backend.lower() in {"pdf","ps","svg","template"})
|
|
190
|
+
if not _is_noninteractive:
|
|
191
|
+
plt.show()
|
|
192
|
+
else:
|
|
193
|
+
print(f"Matplotlib backend '{_backend}' is non-interactive; use --out to save the figure.")
|
|
128
194
|
return 0
|
|
129
195
|
|
|
130
196
|
except Exception as e:
|
|
@@ -333,7 +399,9 @@ def handle_gc_mode(args) -> int:
|
|
|
333
399
|
_backend = plt.get_backend()
|
|
334
400
|
except Exception:
|
|
335
401
|
_backend = "unknown"
|
|
336
|
-
|
|
402
|
+
# TkAgg, QtAgg, Qt5Agg, WXAgg, MacOSX etc. are interactive
|
|
403
|
+
_interactive_backends = {"tkagg", "qt5agg", "qt4agg", "qtagg", "wxagg", "macosx", "gtk3agg", "gtk4agg", "wx", "qt", "gtk", "gtk3", "gtk4"}
|
|
404
|
+
_is_noninteractive = isinstance(_backend, str) and (_backend.lower() not in _interactive_backends) and ("agg" in _backend.lower() or _backend.lower() in {"pdf","ps","svg","template"})
|
|
337
405
|
if _is_noninteractive:
|
|
338
406
|
print(f"Matplotlib backend '{_backend}' is non-interactive; a window cannot be shown.")
|
|
339
407
|
print("Tips: unset MPLBACKEND or set a GUI backend")
|
|
@@ -355,7 +423,10 @@ def handle_gc_mode(args) -> int:
|
|
|
355
423
|
_backend = plt.get_backend()
|
|
356
424
|
except Exception:
|
|
357
425
|
_backend = "unknown"
|
|
358
|
-
|
|
426
|
+
# TkAgg, QtAgg, Qt5Agg, WXAgg, MacOSX etc. are interactive
|
|
427
|
+
_interactive_backends = {"tkagg", "qt5agg", "qt4agg", "qtagg", "wxagg", "macosx", "gtk3agg", "gtk4agg", "wx", "qt", "gtk", "gtk3", "gtk4"}
|
|
428
|
+
_is_noninteractive = isinstance(_backend, str) and (_backend.lower() not in _interactive_backends) and ("agg" in _backend.lower() or _backend.lower() in {"pdf","ps","svg","template"})
|
|
429
|
+
if not _is_noninteractive:
|
|
359
430
|
plt.show()
|
|
360
431
|
else:
|
|
361
432
|
print(f"Matplotlib backend '{_backend}' is non-interactive; use --out to save the figure.")
|
|
@@ -1011,6 +1011,7 @@ def dump_ec_session(
|
|
|
1011
1011
|
'tick_widths': tick_widths,
|
|
1012
1012
|
'spines': spines_state,
|
|
1013
1013
|
'titles': titles,
|
|
1014
|
+
'mode': getattr(ax, '_is_dqdv_mode', None), # Store dQdV mode flag
|
|
1014
1015
|
}
|
|
1015
1016
|
if skip_confirm:
|
|
1016
1017
|
target = filename
|
|
@@ -1316,6 +1317,15 @@ def load_ec_session(filename: str):
|
|
|
1316
1317
|
ax._right_ylabel_on = False
|
|
1317
1318
|
except Exception:
|
|
1318
1319
|
pass
|
|
1320
|
+
|
|
1321
|
+
# Restore mode flag (e.g., dQdV mode)
|
|
1322
|
+
try:
|
|
1323
|
+
mode = sess.get('mode')
|
|
1324
|
+
if mode is not None:
|
|
1325
|
+
ax._is_dqdv_mode = bool(mode)
|
|
1326
|
+
except Exception:
|
|
1327
|
+
pass
|
|
1328
|
+
|
|
1319
1329
|
try:
|
|
1320
1330
|
fig.canvas.draw()
|
|
1321
1331
|
except Exception:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "batplot"
|
|
7
|
-
version = "1.1.
|
|
7
|
+
version = "1.1.4"
|
|
8
8
|
description = "Interactive plotting for XRD, PDF, and XAS data (.xye, .xy, .qye, .dat, .csv, .gr, .nor, .chik, .chir)"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Tian Dai", email = "tianda@uio.no" }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|