batplot 1.8.5__py3-none-any.whl → 1.8.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- batplot/__init__.py +1 -1
- batplot/batplot.py +125 -53
- batplot/cpc_interactive.py +286 -140
- batplot/electrochem_interactive.py +57 -44
- batplot/interactive.py +216 -2
- batplot/modes.py +12 -11
- batplot/operando_ec_interactive.py +158 -14
- batplot/session.py +61 -9
- batplot/style.py +109 -19
- {batplot-1.8.5.dist-info → batplot-1.8.6.dist-info}/METADATA +1 -1
- {batplot-1.8.5.dist-info → batplot-1.8.6.dist-info}/RECORD +15 -15
- {batplot-1.8.5.dist-info → batplot-1.8.6.dist-info}/WHEEL +0 -0
- {batplot-1.8.5.dist-info → batplot-1.8.6.dist-info}/entry_points.txt +0 -0
- {batplot-1.8.5.dist-info → batplot-1.8.6.dist-info}/licenses/LICENSE +0 -0
- {batplot-1.8.5.dist-info → batplot-1.8.6.dist-info}/top_level.txt +0 -0
|
@@ -374,7 +374,7 @@ def _apply_stored_smooth_settings(cycle_lines: Dict[int, Dict[str, Optional[obje
|
|
|
374
374
|
ln._smooth_applied = True
|
|
375
375
|
|
|
376
376
|
|
|
377
|
-
def _print_menu(n_cycles: int, is_dqdv: bool = False):
|
|
377
|
+
def _print_menu(n_cycles: int, is_dqdv: bool = False, fig=None):
|
|
378
378
|
# Three-column menu similar to operando: Styles | Geometries | Options
|
|
379
379
|
# Use dynamic column widths for clean alignment.
|
|
380
380
|
col1 = [
|
|
@@ -405,6 +405,19 @@ def _print_menu(n_cycles: int, is_dqdv: bool = False):
|
|
|
405
405
|
"b: undo",
|
|
406
406
|
"q: quit",
|
|
407
407
|
]
|
|
408
|
+
|
|
409
|
+
# Conditional overwrite shortcuts under (Options)
|
|
410
|
+
if fig is not None:
|
|
411
|
+
last_session = getattr(fig, "_last_session_save_path", None)
|
|
412
|
+
last_style = getattr(fig, "_last_style_export_path", None)
|
|
413
|
+
last_figure = getattr(fig, "_last_figure_export_path", None)
|
|
414
|
+
if last_session:
|
|
415
|
+
col3.append("os: overwrite session")
|
|
416
|
+
if last_style:
|
|
417
|
+
col3.append("ops: overwrite style")
|
|
418
|
+
col3.append("opsg: overwrite style+geom")
|
|
419
|
+
if last_figure:
|
|
420
|
+
col3.append("oe: overwrite figure")
|
|
408
421
|
# Compute widths (min width prevents overly narrow columns)
|
|
409
422
|
w1 = max(len("(Styles)"), *(len(s) for s in col1), 18)
|
|
410
423
|
w2 = max(len("(Geometries)"), *(len(s) for s in col2), 12)
|
|
@@ -1640,7 +1653,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1640
1653
|
print("Undo: restored previous state.")
|
|
1641
1654
|
except Exception as e:
|
|
1642
1655
|
print(f"Undo failed: {e}")
|
|
1643
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
1656
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
1644
1657
|
while True:
|
|
1645
1658
|
try:
|
|
1646
1659
|
key = _safe_input("Press a key: ").strip().lower()
|
|
@@ -1657,18 +1670,18 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1657
1670
|
if confirm == 'y':
|
|
1658
1671
|
break
|
|
1659
1672
|
else:
|
|
1660
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
1673
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
1661
1674
|
continue
|
|
1662
1675
|
elif key == 'b':
|
|
1663
1676
|
restore_state()
|
|
1664
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
1677
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
1665
1678
|
continue
|
|
1666
1679
|
elif key == 'e':
|
|
1667
1680
|
# Export current figure to a file; default extension .svg if missing
|
|
1668
1681
|
try:
|
|
1669
1682
|
base_path = choose_save_path(source_paths, purpose="figure export")
|
|
1670
1683
|
if not base_path:
|
|
1671
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
1684
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
1672
1685
|
continue
|
|
1673
1686
|
# List existing figure files in Figures/ subdirectory
|
|
1674
1687
|
fig_extensions = ('.svg', '.png', '.jpg', '.jpeg', '.pdf', '.eps', '.tif', '.tiff')
|
|
@@ -1690,7 +1703,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1690
1703
|
else:
|
|
1691
1704
|
fname = _safe_input("Export filename (default .svg if no extension) or number to overwrite (q=cancel): ").strip()
|
|
1692
1705
|
if not fname or fname.lower() == 'q':
|
|
1693
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
1706
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
1694
1707
|
continue
|
|
1695
1708
|
|
|
1696
1709
|
already_confirmed = False # Initialize for new filename case
|
|
@@ -1698,15 +1711,15 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1698
1711
|
if fname.lower() == 'o':
|
|
1699
1712
|
if not last_figure_path:
|
|
1700
1713
|
print("No previous export found.")
|
|
1701
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
1714
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
1702
1715
|
continue
|
|
1703
1716
|
if not os.path.exists(last_figure_path):
|
|
1704
1717
|
print(f"Previous export file not found: {last_figure_path}")
|
|
1705
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
1718
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
1706
1719
|
continue
|
|
1707
1720
|
yn = _safe_input(f"Overwrite '{os.path.basename(last_figure_path)}'? (y/n): ").strip().lower()
|
|
1708
1721
|
if yn != 'y':
|
|
1709
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
1722
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
1710
1723
|
continue
|
|
1711
1724
|
target = last_figure_path
|
|
1712
1725
|
already_confirmed = True
|
|
@@ -1718,13 +1731,13 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1718
1731
|
name = files[idx-1]
|
|
1719
1732
|
yn = _safe_input(f"Overwrite '{name}'? (y/n): ").strip().lower()
|
|
1720
1733
|
if yn != 'y':
|
|
1721
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
1734
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
1722
1735
|
continue
|
|
1723
1736
|
target = file_list[idx-1][1] # Full path from list
|
|
1724
1737
|
already_confirmed = True
|
|
1725
1738
|
else:
|
|
1726
1739
|
print("Invalid number.")
|
|
1727
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
1740
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
1728
1741
|
continue
|
|
1729
1742
|
else:
|
|
1730
1743
|
root, ext = os.path.splitext(fname)
|
|
@@ -1807,7 +1820,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1807
1820
|
print(f"Export failed: {e}")
|
|
1808
1821
|
except Exception as e:
|
|
1809
1822
|
print(f"Export failed: {e}")
|
|
1810
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
1823
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
1811
1824
|
continue
|
|
1812
1825
|
elif key == 'h':
|
|
1813
1826
|
# Legend submenu: toggle visibility and move legend in inches relative to canvas center
|
|
@@ -1989,7 +2002,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1989
2002
|
print("Unknown option.")
|
|
1990
2003
|
except Exception:
|
|
1991
2004
|
pass
|
|
1992
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
2005
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
1993
2006
|
continue
|
|
1994
2007
|
elif key == 'p':
|
|
1995
2008
|
# Print/export style or style+geometry
|
|
@@ -2097,14 +2110,14 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2097
2110
|
print("Unknown choice.")
|
|
2098
2111
|
except Exception as e:
|
|
2099
2112
|
print(f"Error in style submenu: {e}")
|
|
2100
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
2113
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
2101
2114
|
continue
|
|
2102
2115
|
elif key == 'i':
|
|
2103
2116
|
# Import style from .bps/.bpsg/.bpcfg
|
|
2104
2117
|
try:
|
|
2105
2118
|
path = choose_style_file(source_paths, purpose="style import")
|
|
2106
2119
|
if not path:
|
|
2107
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
2120
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
2108
2121
|
continue
|
|
2109
2122
|
push_state("import-style")
|
|
2110
2123
|
with open(path, 'r', encoding='utf-8') as f:
|
|
@@ -2114,7 +2127,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2114
2127
|
kind = cfg.get('kind', '')
|
|
2115
2128
|
if kind not in ('ec_style', 'ec_style_geom'):
|
|
2116
2129
|
print("Not an EC style file.")
|
|
2117
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
2130
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
2118
2131
|
continue
|
|
2119
2132
|
|
|
2120
2133
|
# Enforce compatibility between style/geom ro state and current figure ro state
|
|
@@ -2126,7 +2139,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2126
2139
|
else:
|
|
2127
2140
|
print("Warning: EC style/geometry file was saved without --ro; current plot was created with --ro.")
|
|
2128
2141
|
print("Not applying EC style/geometry to avoid corrupting axis orientation.")
|
|
2129
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
2142
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
2130
2143
|
continue
|
|
2131
2144
|
|
|
2132
2145
|
has_geometry = (kind == 'ec_style_geom' and 'geometry' in cfg)
|
|
@@ -2463,7 +2476,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2463
2476
|
|
|
2464
2477
|
except Exception as e:
|
|
2465
2478
|
print(f"Error importing style: {e}")
|
|
2466
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
2479
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
2467
2480
|
continue
|
|
2468
2481
|
elif key == 'l':
|
|
2469
2482
|
# Line widths submenu: curves vs frame/ticks
|
|
@@ -2691,7 +2704,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2691
2704
|
print("Unknown option.")
|
|
2692
2705
|
except Exception as e:
|
|
2693
2706
|
print(f"Error in line submenu: {e}")
|
|
2694
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
2707
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
2695
2708
|
continue
|
|
2696
2709
|
elif key == 'k':
|
|
2697
2710
|
# Spine colors (w=top, a=left, s=bottom, d=right)
|
|
@@ -2749,7 +2762,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2749
2762
|
fig.canvas.draw()
|
|
2750
2763
|
except Exception as e:
|
|
2751
2764
|
print(f"Error in spine color menu: {e}")
|
|
2752
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
2765
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
2753
2766
|
continue
|
|
2754
2767
|
elif key == 'r':
|
|
2755
2768
|
# Rename axis labels
|
|
@@ -2819,7 +2832,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2819
2832
|
fig.canvas.draw_idle()
|
|
2820
2833
|
except Exception as e:
|
|
2821
2834
|
print(f"Error renaming axes: {e}")
|
|
2822
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
2835
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
2823
2836
|
continue
|
|
2824
2837
|
elif key == 't':
|
|
2825
2838
|
# Unified WASD: w/a/s/d x 1..5 => spine, ticks, minor, labels, title
|
|
@@ -3015,7 +3028,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3015
3028
|
fig.canvas.draw_idle()
|
|
3016
3029
|
except Exception as e:
|
|
3017
3030
|
print(f"Error in WASD tick visibility menu: {e}")
|
|
3018
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
3031
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
3019
3032
|
continue
|
|
3020
3033
|
elif key == 's':
|
|
3021
3034
|
try:
|
|
@@ -3023,7 +3036,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3023
3036
|
last_session_path = getattr(fig, '_last_session_save_path', None)
|
|
3024
3037
|
folder = choose_save_path(source_paths, purpose="EC session save")
|
|
3025
3038
|
if not folder:
|
|
3026
|
-
_print_menu(len(all_cycles), is_dqdv); continue
|
|
3039
|
+
_print_menu(len(all_cycles), is_dqdv, fig); continue
|
|
3027
3040
|
print(f"\nChosen path: {folder}")
|
|
3028
3041
|
try:
|
|
3029
3042
|
files = sorted([f for f in os.listdir(folder) if f.lower().endswith('.pkl')])
|
|
@@ -3044,35 +3057,35 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3044
3057
|
prompt = "Enter new filename (no ext needed) or number to overwrite (q=cancel): "
|
|
3045
3058
|
choice = _safe_input(prompt).strip()
|
|
3046
3059
|
if not choice or choice.lower() == 'q':
|
|
3047
|
-
_print_menu(len(all_cycles), is_dqdv); continue
|
|
3060
|
+
_print_menu(len(all_cycles), is_dqdv, fig); continue
|
|
3048
3061
|
if choice.lower() == 'o':
|
|
3049
3062
|
# Overwrite last saved session
|
|
3050
3063
|
if not last_session_path:
|
|
3051
3064
|
print("No previous save found.")
|
|
3052
|
-
_print_menu(len(all_cycles), is_dqdv); continue
|
|
3065
|
+
_print_menu(len(all_cycles), is_dqdv, fig); continue
|
|
3053
3066
|
if not os.path.exists(last_session_path):
|
|
3054
3067
|
print(f"Previous save file not found: {last_session_path}")
|
|
3055
|
-
_print_menu(len(all_cycles), is_dqdv); continue
|
|
3068
|
+
_print_menu(len(all_cycles), is_dqdv, fig); continue
|
|
3056
3069
|
yn = _safe_input(f"Overwrite '{os.path.basename(last_session_path)}'? (y/n): ").strip().lower()
|
|
3057
3070
|
if yn != 'y':
|
|
3058
|
-
_print_menu(len(all_cycles), is_dqdv); continue
|
|
3071
|
+
_print_menu(len(all_cycles), is_dqdv, fig); continue
|
|
3059
3072
|
dump_ec_session(last_session_path, fig=fig, ax=ax, cycle_lines=cycle_lines, skip_confirm=True)
|
|
3060
3073
|
print(f"Overwritten session to {last_session_path}")
|
|
3061
|
-
_print_menu(len(all_cycles), is_dqdv); continue
|
|
3074
|
+
_print_menu(len(all_cycles), is_dqdv, fig); continue
|
|
3062
3075
|
if choice.isdigit() and files:
|
|
3063
3076
|
idx = int(choice)
|
|
3064
3077
|
if 1 <= idx <= len(files):
|
|
3065
3078
|
name = files[idx-1]
|
|
3066
3079
|
yn = _safe_input(f"Overwrite '{name}'? (y/n): ").strip().lower()
|
|
3067
3080
|
if yn != 'y':
|
|
3068
|
-
_print_menu(len(all_cycles), is_dqdv); continue
|
|
3081
|
+
_print_menu(len(all_cycles), is_dqdv, fig); continue
|
|
3069
3082
|
target = os.path.join(folder, name)
|
|
3070
3083
|
dump_ec_session(target, fig=fig, ax=ax, cycle_lines=cycle_lines, skip_confirm=True)
|
|
3071
3084
|
fig._last_session_save_path = target
|
|
3072
|
-
_print_menu(len(all_cycles), is_dqdv); continue
|
|
3085
|
+
_print_menu(len(all_cycles), is_dqdv, fig); continue
|
|
3073
3086
|
else:
|
|
3074
3087
|
print("Invalid number.")
|
|
3075
|
-
_print_menu(len(all_cycles), is_dqdv); continue
|
|
3088
|
+
_print_menu(len(all_cycles), is_dqdv, fig); continue
|
|
3076
3089
|
if choice.lower() != 'o':
|
|
3077
3090
|
name = choice
|
|
3078
3091
|
root, ext = os.path.splitext(name)
|
|
@@ -3082,12 +3095,12 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3082
3095
|
if os.path.exists(target):
|
|
3083
3096
|
yn = _safe_input(f"'{os.path.basename(target)}' exists. Overwrite? (y/n): ").strip().lower()
|
|
3084
3097
|
if yn != 'y':
|
|
3085
|
-
_print_menu(len(all_cycles), is_dqdv); continue
|
|
3098
|
+
_print_menu(len(all_cycles), is_dqdv, fig); continue
|
|
3086
3099
|
dump_ec_session(target, fig=fig, ax=ax, cycle_lines=cycle_lines, skip_confirm=True)
|
|
3087
3100
|
fig._last_session_save_path = target
|
|
3088
3101
|
except Exception as e:
|
|
3089
3102
|
print(f"Save failed: {e}")
|
|
3090
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
3103
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
3091
3104
|
continue
|
|
3092
3105
|
elif key == 'c':
|
|
3093
3106
|
# Show current palette if one is applied (this is informational only)
|
|
@@ -3123,7 +3136,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3123
3136
|
continue
|
|
3124
3137
|
if line.lower() == 'u':
|
|
3125
3138
|
manage_user_colors(fig)
|
|
3126
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
3139
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
3127
3140
|
continue
|
|
3128
3141
|
tokens = line.replace(',', ' ').split()
|
|
3129
3142
|
mode, cycles, mapping, palette, use_all = _parse_cycle_tokens(tokens, fig)
|
|
@@ -3252,13 +3265,13 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3252
3265
|
if ignored:
|
|
3253
3266
|
print("Ignored cycles:", ", ".join(str(c) for c in ignored))
|
|
3254
3267
|
# Show the menu again after completing the command
|
|
3255
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
3268
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
3256
3269
|
continue
|
|
3257
3270
|
elif key == 'a':
|
|
3258
3271
|
# X-axis submenu: number-of-ions vs capacity (not available in dQdV mode)
|
|
3259
3272
|
if is_dqdv:
|
|
3260
3273
|
print("Capacity/ion conversion is not available in dQ/dV mode.")
|
|
3261
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
3274
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
3262
3275
|
continue
|
|
3263
3276
|
# X-axis submenu: number-of-ions vs capacity
|
|
3264
3277
|
while True:
|
|
@@ -3399,7 +3412,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3399
3412
|
fig.canvas.draw()
|
|
3400
3413
|
except Exception:
|
|
3401
3414
|
fig.canvas.draw_idle()
|
|
3402
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
3415
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
3403
3416
|
continue
|
|
3404
3417
|
elif key == 'f':
|
|
3405
3418
|
# Font submenu with numbered options
|
|
@@ -3470,7 +3483,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3470
3483
|
print("Size must be positive.")
|
|
3471
3484
|
except Exception:
|
|
3472
3485
|
print("Invalid size.")
|
|
3473
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
3486
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
3474
3487
|
continue
|
|
3475
3488
|
elif key == 'x':
|
|
3476
3489
|
# X-axis: set limits only
|
|
@@ -3554,7 +3567,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3554
3567
|
fig.canvas.draw()
|
|
3555
3568
|
except Exception:
|
|
3556
3569
|
print("Invalid limits, ignored.")
|
|
3557
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
3570
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
3558
3571
|
continue
|
|
3559
3572
|
elif key == 'y':
|
|
3560
3573
|
# Y-axis: set limits only
|
|
@@ -3638,7 +3651,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3638
3651
|
fig.canvas.draw()
|
|
3639
3652
|
except Exception:
|
|
3640
3653
|
print("Invalid limits, ignored.")
|
|
3641
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
3654
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
3642
3655
|
continue
|
|
3643
3656
|
elif key == 'g':
|
|
3644
3657
|
# Geometry submenu: plot frame vs canvas (scales moved to separate keys)
|
|
@@ -3667,13 +3680,13 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3667
3680
|
fig.canvas.draw()
|
|
3668
3681
|
except Exception:
|
|
3669
3682
|
fig.canvas.draw_idle()
|
|
3670
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
3683
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
3671
3684
|
continue
|
|
3672
3685
|
elif key == 'sm':
|
|
3673
3686
|
# dQ/dV smoothing utilities (only available in dQdV mode)
|
|
3674
3687
|
if not is_dqdv:
|
|
3675
3688
|
print("Smoothing is only available in dQ/dV mode.")
|
|
3676
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
3689
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
3677
3690
|
continue
|
|
3678
3691
|
while True:
|
|
3679
3692
|
print("\n\033[1mdQ/dV Data Filtering (Neware method)\033[0m")
|
|
@@ -4033,11 +4046,11 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
4033
4046
|
print("Invalid number.")
|
|
4034
4047
|
continue
|
|
4035
4048
|
print("Unknown command. Use a/o/r/q.")
|
|
4036
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
4049
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
4037
4050
|
continue
|
|
4038
4051
|
else:
|
|
4039
4052
|
print("Unknown command.")
|
|
4040
|
-
_print_menu(len(all_cycles), is_dqdv)
|
|
4053
|
+
_print_menu(len(all_cycles), is_dqdv, fig)
|
|
4041
4054
|
|
|
4042
4055
|
|
|
4043
4056
|
def _get_geometry_snapshot(fig, ax) -> Dict:
|
batplot/interactive.py
CHANGED
|
@@ -260,6 +260,18 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
260
260
|
col1.append("j: CIF titles")
|
|
261
261
|
col2 = ["a: rearrange", "o: offset", "r: rename", "x: change X", "y: change Y", "d: derivative"]
|
|
262
262
|
col3 = ["v: find peaks", "n: crosshair", "p: print(export) style/geom", "i: import style/geom", "e: export figure", "s: save project", "b: undo", "q: quit"]
|
|
263
|
+
|
|
264
|
+
# Conditional overwrite shortcuts under (Options)
|
|
265
|
+
last_session = getattr(fig, "_last_session_save_path", None)
|
|
266
|
+
last_style = getattr(fig, "_last_style_export_path", None)
|
|
267
|
+
last_figure = getattr(fig, "_last_figure_export_path", None)
|
|
268
|
+
if last_session:
|
|
269
|
+
col3.append("os: overwrite session")
|
|
270
|
+
if last_style:
|
|
271
|
+
col3.append("ops: overwrite style")
|
|
272
|
+
col3.append("opsg: overwrite style+geom")
|
|
273
|
+
if last_figure:
|
|
274
|
+
col3.append("oe: overwrite figure")
|
|
263
275
|
|
|
264
276
|
# Hide offset/y-range in stack mode
|
|
265
277
|
if args.stack:
|
|
@@ -909,7 +921,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
909
921
|
)
|
|
910
922
|
|
|
911
923
|
# NEW: export current style to .bpcfg
|
|
912
|
-
def export_style_config(filename, base_path=None, overwrite_path=None):
|
|
924
|
+
def export_style_config(filename, base_path=None, overwrite_path=None, force_kind=None):
|
|
913
925
|
cts = getattr(_bp, 'cif_tick_series', None) if _bp is not None else None
|
|
914
926
|
show_titles = bool(getattr(_bp, 'show_cif_titles', True)) if _bp is not None else True
|
|
915
927
|
from .style import export_style_config as _export_style_config
|
|
@@ -928,6 +940,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
928
940
|
base_path,
|
|
929
941
|
show_cif_titles=show_titles,
|
|
930
942
|
overwrite_path=overwrite_path,
|
|
943
|
+
force_kind=force_kind,
|
|
931
944
|
)
|
|
932
945
|
|
|
933
946
|
# NEW: apply imported style config (restricted application)
|
|
@@ -2079,6 +2092,141 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2079
2092
|
except Exception as e:
|
|
2080
2093
|
print(f"Error toggling crosshair: {e}")
|
|
2081
2094
|
continue
|
|
2095
|
+
elif key == 'os':
|
|
2096
|
+
# Quick overwrite of last saved session (.pkl)
|
|
2097
|
+
try:
|
|
2098
|
+
last_session_path = getattr(fig, '_last_session_save_path', None)
|
|
2099
|
+
if not last_session_path:
|
|
2100
|
+
print("No previous session save found.")
|
|
2101
|
+
continue
|
|
2102
|
+
if not os.path.exists(last_session_path):
|
|
2103
|
+
print(f"Previous save file not found: {last_session_path}")
|
|
2104
|
+
continue
|
|
2105
|
+
yn = _safe_input(f"Overwrite session '{os.path.basename(last_session_path)}'? (y/n): ").strip().lower()
|
|
2106
|
+
if yn != 'y':
|
|
2107
|
+
print("Canceled.")
|
|
2108
|
+
continue
|
|
2109
|
+
_bp_dump_session(
|
|
2110
|
+
last_session_path,
|
|
2111
|
+
fig=fig,
|
|
2112
|
+
ax=ax,
|
|
2113
|
+
x_data_list=x_data_list,
|
|
2114
|
+
y_data_list=y_data_list,
|
|
2115
|
+
orig_y=orig_y,
|
|
2116
|
+
offsets_list=offsets_list,
|
|
2117
|
+
labels=labels,
|
|
2118
|
+
delta=delta,
|
|
2119
|
+
args=args,
|
|
2120
|
+
tick_state=tick_state,
|
|
2121
|
+
cif_tick_series=(getattr(_bp, 'cif_tick_series', None) if _bp is not None else None),
|
|
2122
|
+
cif_hkl_map=(getattr(_bp, 'cif_hkl_map', None) if _bp is not None else None),
|
|
2123
|
+
cif_hkl_label_map=(getattr(_bp, 'cif_hkl_label_map', None) if _bp is not None else None),
|
|
2124
|
+
show_cif_hkl=(bool(getattr(_bp, 'show_cif_hkl', False)) if _bp is not None else False),
|
|
2125
|
+
show_cif_titles=(bool(getattr(_bp, 'show_cif_titles', True)) if _bp is not None else True),
|
|
2126
|
+
skip_confirm=True,
|
|
2127
|
+
)
|
|
2128
|
+
fig._last_session_save_path = last_session_path
|
|
2129
|
+
print(f"Overwritten session to {last_session_path}")
|
|
2130
|
+
except Exception as e:
|
|
2131
|
+
print(f"Error overwriting session: {e}")
|
|
2132
|
+
continue
|
|
2133
|
+
elif key in ('ops', 'opsg'):
|
|
2134
|
+
# Quick overwrite of last exported style file (.bps / .bpsg)
|
|
2135
|
+
try:
|
|
2136
|
+
last_style_path = getattr(fig, '_last_style_export_path', None)
|
|
2137
|
+
if not last_style_path:
|
|
2138
|
+
print("No previous style export found.")
|
|
2139
|
+
continue
|
|
2140
|
+
if not os.path.exists(last_style_path):
|
|
2141
|
+
print(f"Previous style file not found: {last_style_path}")
|
|
2142
|
+
continue
|
|
2143
|
+
if key == 'ops':
|
|
2144
|
+
mode = 'ps'
|
|
2145
|
+
label = "style-only"
|
|
2146
|
+
else:
|
|
2147
|
+
mode = 'psg'
|
|
2148
|
+
label = "style+geometry"
|
|
2149
|
+
yn = _safe_input(
|
|
2150
|
+
f"Overwrite {label} file '{os.path.basename(last_style_path)}'? (y/n): "
|
|
2151
|
+
).strip().lower()
|
|
2152
|
+
if yn != 'y':
|
|
2153
|
+
print("Canceled.")
|
|
2154
|
+
continue
|
|
2155
|
+
exported = export_style_config(
|
|
2156
|
+
None,
|
|
2157
|
+
base_path=None,
|
|
2158
|
+
overwrite_path=last_style_path,
|
|
2159
|
+
force_kind=mode,
|
|
2160
|
+
)
|
|
2161
|
+
if exported:
|
|
2162
|
+
fig._last_style_export_path = exported
|
|
2163
|
+
print(f"Overwritten {label} style to {exported}")
|
|
2164
|
+
except Exception as e:
|
|
2165
|
+
print(f"Error overwriting style: {e}")
|
|
2166
|
+
continue
|
|
2167
|
+
elif key == 'oe':
|
|
2168
|
+
# Quick overwrite of last exported figure
|
|
2169
|
+
try:
|
|
2170
|
+
last_figure_path = getattr(fig, '_last_figure_export_path', None)
|
|
2171
|
+
if not last_figure_path:
|
|
2172
|
+
print("No previous figure export found.")
|
|
2173
|
+
continue
|
|
2174
|
+
if not os.path.exists(last_figure_path):
|
|
2175
|
+
print(f"Previous export file not found: {last_figure_path}")
|
|
2176
|
+
continue
|
|
2177
|
+
yn = _safe_input(
|
|
2178
|
+
f"Overwrite figure '{os.path.basename(last_figure_path)}'? (y/n): "
|
|
2179
|
+
).strip().lower()
|
|
2180
|
+
if yn != 'y':
|
|
2181
|
+
print("Canceled.")
|
|
2182
|
+
continue
|
|
2183
|
+
export_target = last_figure_path
|
|
2184
|
+
from .utils import ensure_exact_case_filename
|
|
2185
|
+
export_target = ensure_exact_case_filename(export_target)
|
|
2186
|
+
# Temporarily remove numbering for export
|
|
2187
|
+
for i, txt in enumerate(label_text_objects):
|
|
2188
|
+
txt.set_text(labels[i])
|
|
2189
|
+
_, _ext = os.path.splitext(export_target)
|
|
2190
|
+
if _ext.lower() == '.svg':
|
|
2191
|
+
try:
|
|
2192
|
+
_fig_fc = fig.get_facecolor()
|
|
2193
|
+
except Exception:
|
|
2194
|
+
_fig_fc = None
|
|
2195
|
+
try:
|
|
2196
|
+
_ax_fc = ax.get_facecolor()
|
|
2197
|
+
except Exception:
|
|
2198
|
+
_ax_fc = None
|
|
2199
|
+
try:
|
|
2200
|
+
if getattr(fig, 'patch', None) is not None:
|
|
2201
|
+
fig.patch.set_alpha(0.0); fig.patch.set_facecolor('none')
|
|
2202
|
+
if getattr(ax, 'patch', None) is not None:
|
|
2203
|
+
ax.patch.set_alpha(0.0); ax.patch.set_facecolor('none')
|
|
2204
|
+
except Exception:
|
|
2205
|
+
pass
|
|
2206
|
+
try:
|
|
2207
|
+
fig.savefig(export_target, dpi=300, transparent=True, facecolor='none', edgecolor='none')
|
|
2208
|
+
finally:
|
|
2209
|
+
try:
|
|
2210
|
+
if _fig_fc is not None and getattr(fig, 'patch', None) is not None:
|
|
2211
|
+
fig.patch.set_alpha(1.0); fig.patch.set_facecolor(_fig_fc)
|
|
2212
|
+
except Exception:
|
|
2213
|
+
pass
|
|
2214
|
+
try:
|
|
2215
|
+
if _ax_fc is not None and getattr(ax, 'patch', None) is not None:
|
|
2216
|
+
ax.patch.set_alpha(1.0); ax.patch.set_facecolor(_ax_fc)
|
|
2217
|
+
except Exception:
|
|
2218
|
+
pass
|
|
2219
|
+
else:
|
|
2220
|
+
fig.savefig(export_target, dpi=300)
|
|
2221
|
+
print(f"Figure saved to {export_target}")
|
|
2222
|
+
fig._last_figure_export_path = export_target
|
|
2223
|
+
# Restore numbering
|
|
2224
|
+
for i, txt in enumerate(label_text_objects):
|
|
2225
|
+
txt.set_text(f"{i+1}: {labels[i]}")
|
|
2226
|
+
fig.canvas.draw()
|
|
2227
|
+
except Exception as e:
|
|
2228
|
+
print(f"Error overwriting figure: {e}")
|
|
2229
|
+
continue
|
|
2082
2230
|
elif key == 's':
|
|
2083
2231
|
# Save current interactive session with numbered overwrite picker
|
|
2084
2232
|
try:
|
|
@@ -4434,8 +4582,13 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
4434
4582
|
else:
|
|
4435
4583
|
print(f" {_i}: {fname}")
|
|
4436
4584
|
last_style_path = getattr(fig, '_last_style_export_path', None)
|
|
4437
|
-
if
|
|
4585
|
+
n_style = len(style_file_list) if style_file_list else 0
|
|
4586
|
+
if last_style_path and n_style:
|
|
4587
|
+
sub = _safe_input(colorize_prompt("Style submenu: (e=export, o=overwrite last, q=return, r=refresh). Press number to overwrite: ")).strip().lower()
|
|
4588
|
+
elif last_style_path:
|
|
4438
4589
|
sub = _safe_input(colorize_prompt("Style submenu: (e=export, o=overwrite last, q=return, r=refresh): ")).strip().lower()
|
|
4590
|
+
elif n_style:
|
|
4591
|
+
sub = _safe_input(colorize_prompt("Style submenu: (e=export, q=return, r=refresh). Press number to overwrite: ")).strip().lower()
|
|
4439
4592
|
else:
|
|
4440
4593
|
sub = _safe_input(colorize_prompt("Style submenu: (e=export, q=return, r=refresh): ")).strip().lower()
|
|
4441
4594
|
if sub == 'q':
|
|
@@ -4459,6 +4612,19 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
4459
4612
|
fig._last_style_export_path = exported_path
|
|
4460
4613
|
style_menu_active = False
|
|
4461
4614
|
break
|
|
4615
|
+
if sub.isdigit() and n_style and 1 <= int(sub) <= n_style:
|
|
4616
|
+
# Overwrite listed style file by number (same as e → export → number)
|
|
4617
|
+
idx = int(sub) - 1
|
|
4618
|
+
target_path = style_file_list[idx][1]
|
|
4619
|
+
fname = style_file_list[idx][0]
|
|
4620
|
+
yn = _safe_input(f"Overwrite '{fname}'? (y/n): ").strip().lower()
|
|
4621
|
+
if yn != 'y':
|
|
4622
|
+
continue
|
|
4623
|
+
exported_path = export_style_config(None, base_path=None, overwrite_path=target_path)
|
|
4624
|
+
if exported_path:
|
|
4625
|
+
fig._last_style_export_path = exported_path
|
|
4626
|
+
style_menu_active = False
|
|
4627
|
+
break
|
|
4462
4628
|
if sub == 'e':
|
|
4463
4629
|
save_base = choose_save_path(source_file_paths, purpose="style export")
|
|
4464
4630
|
if not save_base:
|
|
@@ -4481,6 +4647,12 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
4481
4647
|
if not fname:
|
|
4482
4648
|
print("Style import canceled.")
|
|
4483
4649
|
continue
|
|
4650
|
+
import os
|
|
4651
|
+
bname = os.path.basename(fname)
|
|
4652
|
+
yn = _safe_input(colorize_prompt(f"Apply style '{bname}'? (y/n): ")).strip().lower()
|
|
4653
|
+
if yn != 'y':
|
|
4654
|
+
print("Style import canceled.")
|
|
4655
|
+
continue
|
|
4484
4656
|
push_state("style-import")
|
|
4485
4657
|
apply_style_config(fname)
|
|
4486
4658
|
except Exception as e:
|
|
@@ -5310,6 +5482,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
5310
5482
|
|
|
5311
5483
|
print("\n--- Peak Report ---")
|
|
5312
5484
|
print(f"X range used: {x_min} .. {x_max} (relative height threshold={min_frac})")
|
|
5485
|
+
all_peak_results = [] # list of (curve_index, label, [(x, y), ...])
|
|
5313
5486
|
for i, (x_arr, y_off) in enumerate(zip(x_data_list, y_data_list)):
|
|
5314
5487
|
# Recover original curve (remove vertical offset)
|
|
5315
5488
|
if i < len(offsets_list):
|
|
@@ -5363,9 +5536,50 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
5363
5536
|
last_idx = pi
|
|
5364
5537
|
|
|
5365
5538
|
print(" Peaks (x, y):")
|
|
5539
|
+
peak_xy_list = []
|
|
5366
5540
|
for pi in peaks:
|
|
5541
|
+
px, py = float(x_sel[pi]), float(y_sel[pi])
|
|
5542
|
+
peak_xy_list.append((px, py))
|
|
5367
5543
|
print(f" x={x_sel[pi]:.6g}, y={y_sel[pi]:.6g}")
|
|
5544
|
+
if peak_xy_list:
|
|
5545
|
+
all_peak_results.append((i + 1, label, peak_xy_list))
|
|
5368
5546
|
print("\n--- End Peak Report ---\n")
|
|
5547
|
+
|
|
5548
|
+
# Export peaks to file
|
|
5549
|
+
if all_peak_results:
|
|
5550
|
+
export_yn = _safe_input("Export peaks to file? (y/n): ").strip().lower()
|
|
5551
|
+
if export_yn == 'y':
|
|
5552
|
+
folder = choose_save_path(source_file_paths, purpose="peak export")
|
|
5553
|
+
if folder:
|
|
5554
|
+
print(f"\nChosen path: {folder}")
|
|
5555
|
+
fname = _safe_input("Export filename (default: peaks.txt): ").strip()
|
|
5556
|
+
if not fname:
|
|
5557
|
+
fname = "peaks.txt"
|
|
5558
|
+
if not fname.endswith('.txt'):
|
|
5559
|
+
fname += '.txt'
|
|
5560
|
+
import os
|
|
5561
|
+
target = fname if os.path.isabs(fname) else os.path.join(folder, fname)
|
|
5562
|
+
do_write = not os.path.exists(target)
|
|
5563
|
+
if os.path.exists(target):
|
|
5564
|
+
ow = _safe_input(f"'{os.path.basename(target)}' exists. Overwrite? (y/n): ").strip().lower()
|
|
5565
|
+
if ow == 'y':
|
|
5566
|
+
do_write = True
|
|
5567
|
+
else:
|
|
5568
|
+
print("Export canceled.")
|
|
5569
|
+
if do_write:
|
|
5570
|
+
try:
|
|
5571
|
+
with open(target, 'w') as f:
|
|
5572
|
+
f.write("# Curve\tLabel\tPeak x\tPeak y\n")
|
|
5573
|
+
for curve_idx, label, peak_xy_list in all_peak_results:
|
|
5574
|
+
for px, py in peak_xy_list:
|
|
5575
|
+
f.write(f"{curve_idx}\t{label}\t{px:.6g}\t{py:.6g}\n")
|
|
5576
|
+
total_peaks = sum(len(pairs) for _, _, pairs in all_peak_results)
|
|
5577
|
+
print(f"Peak positions exported to {target}")
|
|
5578
|
+
print(f"Found {total_peaks} peaks across {len(all_peak_results)} curves.")
|
|
5579
|
+
except Exception as e:
|
|
5580
|
+
print(f"Error saving file: {e}")
|
|
5581
|
+
else:
|
|
5582
|
+
print("Export canceled.")
|
|
5369
5583
|
except Exception as e:
|
|
5370
5584
|
print(f"Error finding peaks: {e}")
|
|
5371
5585
|
|