batplot 1.4.0__tar.gz → 1.4.2__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.4.0 → batplot-1.4.2}/PKG-INFO +1 -1
- {batplot-1.4.0 → batplot-1.4.2}/batplot/args.py +18 -8
- {batplot-1.4.0 → batplot-1.4.2}/batplot/batch.py +1 -1
- {batplot-1.4.0 → batplot-1.4.2}/batplot/batplot.py +61 -20
- {batplot-1.4.0 → batplot-1.4.2}/batplot/cli.py +4 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot/cpc_interactive.py +65 -15
- {batplot-1.4.0 → batplot-1.4.2}/batplot/electrochem_interactive.py +122 -22
- {batplot-1.4.0 → batplot-1.4.2}/batplot/interactive.py +148 -45
- {batplot-1.4.0 → batplot-1.4.2}/batplot/operando_ec_interactive.py +76 -20
- {batplot-1.4.0 → batplot-1.4.2}/batplot/session.py +2 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot/style.py +11 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot.egg-info/PKG-INFO +1 -1
- {batplot-1.4.0 → batplot-1.4.2}/pyproject.toml +1 -1
- {batplot-1.4.0 → batplot-1.4.2}/LICENSE +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/README.md +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot/__init__.py +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot/batplot_new.py +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot/cif.py +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot/converters.py +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot/modes.py +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot/operando.py +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot/plotting.py +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot/readers.py +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot/ui.py +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot/utils.py +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot.egg-info/SOURCES.txt +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot.egg-info/dependency_links.txt +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot.egg-info/entry_points.txt +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot.egg-info/requires.txt +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/batplot.egg-info/top_level.txt +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/setup.cfg +0 -0
- {batplot-1.4.0 → batplot-1.4.2}/setup.py +0 -0
|
@@ -104,14 +104,16 @@ def _print_general_help() -> None:
|
|
|
104
104
|
" • Batch styling: apply .bps/.bpsg files to all exports (use --all flag)\n"
|
|
105
105
|
" • Format option: use --format png/pdf/jpg/etc to change export format\n\n"
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
"More help:\n"
|
|
108
|
+
" batplot -h xy # XY file plotting guide\n"
|
|
109
|
+
" batplot -h ec # Electrochemistry (GC/dQdV/CV/CPC) guide\n"
|
|
110
|
+
" batplot -h op # Operando guide\n"
|
|
111
|
+
" Manual: https://github.com/tiandai-chem/batplot/blob/main/USER_MANUAL.md\n\n"
|
|
112
|
+
"Contact & Updates:\n"
|
|
113
|
+
" Subscribe to batplot-lab@kjemi.uio.no for updates\n"
|
|
114
|
+
" (If you are not from UiO, send an email to sympa@kjemi.uio.no with the subject line \"subscribe batplot-lab@kjemi.uio.no your-name\")\n"
|
|
115
|
+
" GitHub: https://github.com/tiandai-chem/batplot\n"
|
|
116
|
+
" Email: tianda@uio.no\n"
|
|
115
117
|
)
|
|
116
118
|
_print_help(msg)
|
|
117
119
|
|
|
@@ -143,6 +145,10 @@ def _print_xy_help() -> None:
|
|
|
143
145
|
" --interactive : open interactive menu for styling, ranges, fonts, export, sessions\n"
|
|
144
146
|
" --delta/-d <float> : spacing between curves, e.g. --delta 0.1\n"
|
|
145
147
|
" --norm : normalize intensity to 0-1 range. Stack mode (--stack) auto-normalizes\n"
|
|
148
|
+
" --chik : EXAFS χ(k) plot (sets labels to k (Å⁻¹) vs χ(k))\n"
|
|
149
|
+
" --kchik : multiply y by x for EXAFS kχ(k) plots (sets labels to k (Å⁻¹) vs kχ(k) (Å⁻¹))\n"
|
|
150
|
+
" --k2chik : multiply y by x² for EXAFS k²χ(k) plots (sets labels to k (Å⁻¹) vs k²χ(k) (Å⁻²))\n"
|
|
151
|
+
" --k3chik : multiply y by x³ for EXAFS k³χ(k) plots (sets labels to k (Å⁻¹) vs k³χ(k) (Å⁻³))\n"
|
|
146
152
|
" --xrange/-r <min> <max> : set x-axis range, e.g. --xrange 0 10\n"
|
|
147
153
|
" --out/-o <filename> : save figure to file, e.g. --out file.svg\n"
|
|
148
154
|
" --xaxis <type> : set x-axis type (Q, 2theta, r, k, energy, rft, time, or user defined)\n"
|
|
@@ -234,6 +240,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
234
240
|
parser.add_argument("--wl", type=float, help=argparse.SUPPRESS)
|
|
235
241
|
parser.add_argument("--fullprof", nargs="+", type=float, help=argparse.SUPPRESS)
|
|
236
242
|
parser.add_argument("--norm", action="store_true", help=argparse.SUPPRESS)
|
|
243
|
+
parser.add_argument("--chik", action="store_true", help=argparse.SUPPRESS)
|
|
244
|
+
parser.add_argument("--kchik", action="store_true", help=argparse.SUPPRESS)
|
|
245
|
+
parser.add_argument("--k2chik", action="store_true", help=argparse.SUPPRESS)
|
|
246
|
+
parser.add_argument("--k3chik", action="store_true", help=argparse.SUPPRESS)
|
|
237
247
|
parser.add_argument("--interactive", action="store_true", help=argparse.SUPPRESS)
|
|
238
248
|
parser.add_argument("--savefig", type=str, help=argparse.SUPPRESS)
|
|
239
249
|
parser.add_argument("--stack", action="store_true", help=argparse.SUPPRESS)
|
|
@@ -285,7 +285,7 @@ def batch_process(directory: str, args):
|
|
|
285
285
|
args._batch_warned_extensions.add(ext)
|
|
286
286
|
print(f" Note: Reading '{ext}' files as 2-column (x, y) data with x-axis = {args.xaxis}")
|
|
287
287
|
else:
|
|
288
|
-
raise ValueError(f"
|
|
288
|
+
raise ValueError(f"Unknown file type: {fname}. Use --xaxis [Q|2theta|r|k|energy|rft] or batplot -h for help.")
|
|
289
289
|
|
|
290
290
|
# Convert to Q if needed
|
|
291
291
|
if axis_mode == 'Q' and ext not in ('.qye', '.gr', '.nor'):
|
|
@@ -1587,6 +1587,12 @@ def batplot_main() -> int:
|
|
|
1587
1587
|
fig._stack_label_at_bottom = stack_label_at_bottom
|
|
1588
1588
|
except Exception:
|
|
1589
1589
|
pass
|
|
1590
|
+
# Restore grid state
|
|
1591
|
+
try:
|
|
1592
|
+
grid_state = bool(sess.get('grid', False))
|
|
1593
|
+
ax.grid(grid_state, color='0.85', linestyle='-', linewidth=0.5, alpha=0.7)
|
|
1594
|
+
except Exception:
|
|
1595
|
+
pass
|
|
1590
1596
|
# CIF tick series (optional)
|
|
1591
1597
|
cif_tick_series = sess.get('cif_tick_series') or []
|
|
1592
1598
|
cif_hkl_map = {k: [tuple(v) for v in val] for k,val in sess.get('cif_hkl_map', {}).items()}
|
|
@@ -1879,7 +1885,7 @@ def batplot_main() -> int:
|
|
|
1879
1885
|
if args.xaxis:
|
|
1880
1886
|
axis_mode = args.xaxis
|
|
1881
1887
|
else:
|
|
1882
|
-
raise ValueError("
|
|
1888
|
+
raise ValueError("Unknown file type. Use: batplot file.txt --xaxis [Q|2theta|r|k|energy|rft] or batplot -h for help.")
|
|
1883
1889
|
elif any_lambda or any_cif:
|
|
1884
1890
|
if args.xaxis and args.xaxis.lower() in ("2theta","two_theta","tth"):
|
|
1885
1891
|
axis_mode = "2theta"
|
|
@@ -1890,7 +1896,7 @@ def batplot_main() -> int:
|
|
|
1890
1896
|
elif args.xaxis:
|
|
1891
1897
|
axis_mode = args.xaxis
|
|
1892
1898
|
else:
|
|
1893
|
-
raise ValueError("
|
|
1899
|
+
raise ValueError("Unknown file type. Use: batplot file.csv --xaxis [Q|2theta|r|k|energy|rft] or batplot -h for help.")
|
|
1894
1900
|
|
|
1895
1901
|
use_Q = axis_mode == "Q"
|
|
1896
1902
|
use_2th = axis_mode == "2theta"
|
|
@@ -2125,6 +2131,24 @@ def batplot_main() -> int:
|
|
|
2125
2131
|
else:
|
|
2126
2132
|
x_plot = x_full
|
|
2127
2133
|
|
|
2134
|
+
# ---- Apply EXAFS k-weighting transformation if requested ----
|
|
2135
|
+
if getattr(args, 'k3chik', False):
|
|
2136
|
+
# Multiply y by x³ for EXAFS k³χ(k) plots
|
|
2137
|
+
y_plot = y_plot * (x_plot ** 3)
|
|
2138
|
+
y_full_raw = y_full_raw * (x_full ** 3)
|
|
2139
|
+
raw_y_full_list[-1] = y_full_raw
|
|
2140
|
+
elif getattr(args, 'k2chik', False):
|
|
2141
|
+
# Multiply y by x² for EXAFS k²χ(k) plots
|
|
2142
|
+
y_plot = y_plot * (x_plot ** 2)
|
|
2143
|
+
y_full_raw = y_full_raw * (x_full ** 2)
|
|
2144
|
+
raw_y_full_list[-1] = y_full_raw
|
|
2145
|
+
elif getattr(args, 'kchik', False):
|
|
2146
|
+
# Multiply y by x for EXAFS kχ(k) plots
|
|
2147
|
+
y_plot = y_plot * x_plot
|
|
2148
|
+
y_full_raw = y_full_raw * x_full
|
|
2149
|
+
raw_y_full_list[-1] = y_full_raw
|
|
2150
|
+
# elif getattr(args, 'chik', False): no multiplication needed, just label change
|
|
2151
|
+
|
|
2128
2152
|
# ---- Normalize (display subset) ----
|
|
2129
2153
|
# Auto-normalize for --stack mode, or explicit --norm flag
|
|
2130
2154
|
should_normalize = args.stack or getattr(args, 'norm', False)
|
|
@@ -2499,26 +2523,43 @@ def batplot_main() -> int:
|
|
|
2499
2523
|
ax._cif_extend_func = extend_cif_tick_series
|
|
2500
2524
|
ax._cif_draw_func = draw_cif_ticks
|
|
2501
2525
|
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
elif
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
elif args
|
|
2510
|
-
x_label =
|
|
2526
|
+
# Handle EXAFS k-weighted χ(k) mode labels
|
|
2527
|
+
if getattr(args, 'k3chik', False):
|
|
2528
|
+
x_label = r"k ($\mathrm{\AA}^{-1}$)"
|
|
2529
|
+
y_label = r"k$^3$χ(k) ($\mathrm{\AA}^{-3}$)"
|
|
2530
|
+
elif getattr(args, 'k2chik', False):
|
|
2531
|
+
x_label = r"k ($\mathrm{\AA}^{-1}$)"
|
|
2532
|
+
y_label = r"k$^2$χ(k) ($\mathrm{\AA}^{-2}$)"
|
|
2533
|
+
elif getattr(args, 'kchik', False):
|
|
2534
|
+
x_label = r"k ($\mathrm{\AA}^{-1}$)"
|
|
2535
|
+
y_label = r"kχ(k) ($\mathrm{\AA}^{-1}$)"
|
|
2536
|
+
elif getattr(args, 'chik', False):
|
|
2537
|
+
x_label = r"k ($\mathrm{\AA}^{-1}$)"
|
|
2538
|
+
y_label = r"χ(k)"
|
|
2511
2539
|
else:
|
|
2512
|
-
x_label = "
|
|
2540
|
+
if use_E: x_label = "Energy (eV)"
|
|
2541
|
+
elif use_r: x_label = r"r (Å)"
|
|
2542
|
+
elif use_k: x_label = r"k ($\mathrm{\AA}^{-1}$)"
|
|
2543
|
+
elif use_rft: x_label = "Radial distance (Å)"
|
|
2544
|
+
elif use_Q: x_label = r"Q ($\mathrm{\AA}^{-1}$)"
|
|
2545
|
+
elif use_2th: x_label = r"$2\theta$ (deg)"
|
|
2546
|
+
elif use_time: x_label = "Time (h)"
|
|
2547
|
+
elif args.xaxis:
|
|
2548
|
+
x_label = str(args.xaxis)
|
|
2549
|
+
else:
|
|
2550
|
+
x_label = "X"
|
|
2551
|
+
|
|
2552
|
+
# Y-axis label: normalized if --stack or --norm, or voltage for time mode
|
|
2553
|
+
should_normalize = args.stack or getattr(args, 'norm', False)
|
|
2554
|
+
if use_time:
|
|
2555
|
+
y_label = "Voltage (V)"
|
|
2556
|
+
elif should_normalize:
|
|
2557
|
+
y_label = "Normalized intensity (a.u.)"
|
|
2558
|
+
else:
|
|
2559
|
+
y_label = "Intensity"
|
|
2560
|
+
|
|
2513
2561
|
ax.set_xlabel(x_label, fontsize=16)
|
|
2514
|
-
|
|
2515
|
-
should_normalize = args.stack or getattr(args, 'norm', False)
|
|
2516
|
-
if use_time:
|
|
2517
|
-
ax.set_ylabel("Voltage (V)", fontsize=16)
|
|
2518
|
-
elif should_normalize:
|
|
2519
|
-
ax.set_ylabel("Normalized intensity (a.u.)", fontsize=16)
|
|
2520
|
-
else:
|
|
2521
|
-
ax.set_ylabel("Intensity", fontsize=16)
|
|
2562
|
+
ax.set_ylabel(y_label, fontsize=16)
|
|
2522
2563
|
|
|
2523
2564
|
# Store originals for axis-title toggle restoration (t menu bn/ln)
|
|
2524
2565
|
try:
|
|
@@ -27,6 +27,10 @@ def main(argv: Optional[list] = None) -> int:
|
|
|
27
27
|
# Import the main batplot function (now refactored to be safe)
|
|
28
28
|
from .batplot import batplot_main
|
|
29
29
|
return batplot_main()
|
|
30
|
+
except ValueError as e:
|
|
31
|
+
# Print clean error message without traceback
|
|
32
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
33
|
+
return 1
|
|
30
34
|
finally:
|
|
31
35
|
if argv is not None:
|
|
32
36
|
sys.argv = old_argv
|
|
@@ -23,6 +23,52 @@ from .ui import (
|
|
|
23
23
|
from .utils import _confirm_overwrite
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def _colorize_menu(text):
|
|
27
|
+
"""Colorize menu items: command in cyan, colon in white, description in default."""
|
|
28
|
+
if ':' not in text:
|
|
29
|
+
return text
|
|
30
|
+
parts = text.split(':', 1)
|
|
31
|
+
cmd = parts[0].strip()
|
|
32
|
+
desc = parts[1].strip() if len(parts) > 1 else ''
|
|
33
|
+
return f"\033[96m{cmd}\033[0m: {desc}" # Cyan for command, default for description
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _colorize_prompt(text):
|
|
37
|
+
"""Colorize commands within input prompts. Handles formats like (s=size, f=family, q=return) or (y/n)."""
|
|
38
|
+
import re
|
|
39
|
+
pattern = r'\(([a-z]+=[^,)]+(?:,\s*[a-z]+=[^,)]+)*|[a-z]+(?:/[a-z]+)+)\)'
|
|
40
|
+
|
|
41
|
+
def colorize_match(match):
|
|
42
|
+
content = match.group(1)
|
|
43
|
+
if '/' in content:
|
|
44
|
+
parts = content.split('/')
|
|
45
|
+
colored_parts = [f"\033[96m{p.strip()}\033[0m" for p in parts]
|
|
46
|
+
return f"({'/'.join(colored_parts)})"
|
|
47
|
+
else:
|
|
48
|
+
parts = content.split(',')
|
|
49
|
+
colored_parts = []
|
|
50
|
+
for part in parts:
|
|
51
|
+
part = part.strip()
|
|
52
|
+
if '=' in part:
|
|
53
|
+
cmd, desc = part.split('=', 1)
|
|
54
|
+
colored_parts.append(f"\033[96m{cmd.strip()}\033[0m={desc.strip()}")
|
|
55
|
+
else:
|
|
56
|
+
colored_parts.append(part)
|
|
57
|
+
return f"({', '.join(colored_parts)})"
|
|
58
|
+
|
|
59
|
+
return re.sub(pattern, colorize_match, text)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _colorize_inline_commands(text):
|
|
63
|
+
"""Colorize inline command examples in help text. Colors quoted examples and specific known commands."""
|
|
64
|
+
import re
|
|
65
|
+
# Color quoted command examples (like 's2 w5 a4', 'w2 w5')
|
|
66
|
+
text = re.sub(r"'([a-z0-9\s_-]+)'", lambda m: f"'\033[96m{m.group(1)}\033[0m'", text)
|
|
67
|
+
# Color specific known commands: q, i, l, list, help, all
|
|
68
|
+
text = re.sub(r'\b(q|i|l|list|help|all)\b(?=\s*[=,]|\s*$)', lambda m: f"\033[96m{m.group(1)}\033[0m", text)
|
|
69
|
+
return text
|
|
70
|
+
|
|
71
|
+
|
|
26
72
|
def _generate_similar_color(base_color):
|
|
27
73
|
"""Generate a similar but distinguishable color for discharge from charge color."""
|
|
28
74
|
try:
|
|
@@ -83,13 +129,17 @@ def _print_menu():
|
|
|
83
129
|
w2 = max(18, *(len(s) for s in col2))
|
|
84
130
|
w3 = max(12, *(len(s) for s in col3))
|
|
85
131
|
rows = max(len(col1), len(col2), len(col3))
|
|
86
|
-
print("\
|
|
87
|
-
print(f" {'(Styles)':<{w1}} {'(Geometries)':<{w2}} {'(Options)':<{w3}}")
|
|
132
|
+
print("\n\033[1mCPC interactive menu:\033[0m") # Bold title
|
|
133
|
+
print(f" \033[93m{'(Styles)':<{w1}}\033[0m \033[93m{'(Geometries)':<{w2}}\033[0m \033[93m{'(Options)':<{w3}}\033[0m") # Yellow headers
|
|
88
134
|
for i in range(rows):
|
|
89
|
-
p1 = col1[i] if i < len(col1) else ""
|
|
90
|
-
p2 = col2[i] if i < len(col2) else ""
|
|
91
|
-
p3 = col3[i] if i < len(col3) else ""
|
|
92
|
-
|
|
135
|
+
p1 = _colorize_menu(col1[i]) if i < len(col1) else ""
|
|
136
|
+
p2 = _colorize_menu(col2[i]) if i < len(col2) else ""
|
|
137
|
+
p3 = _colorize_menu(col3[i]) if i < len(col3) else ""
|
|
138
|
+
# Add padding to account for ANSI escape codes
|
|
139
|
+
pad1 = w1 + (9 if i < len(col1) else 0)
|
|
140
|
+
pad2 = w2 + (9 if i < len(col2) else 0)
|
|
141
|
+
pad3 = w3 + (9 if i < len(col3) else 0)
|
|
142
|
+
print(f" {p1:<{pad1}} {p2:<{pad2}} {p3:<{pad3}}")
|
|
93
143
|
|
|
94
144
|
|
|
95
145
|
def _get_current_file_artists(file_data, current_idx):
|
|
@@ -859,7 +909,7 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
859
909
|
|
|
860
910
|
if key == 'q':
|
|
861
911
|
try:
|
|
862
|
-
confirm = input("Quit CPC interactive? Remember to save! Quit now? (y/n): ").strip().lower()
|
|
912
|
+
confirm = input(_colorize_prompt("Quit CPC interactive? Remember to save! Quit now? (y/n): ")).strip().lower()
|
|
863
913
|
except Exception:
|
|
864
914
|
confirm = 'y'
|
|
865
915
|
if confirm == 'y':
|
|
@@ -1155,9 +1205,9 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
1155
1205
|
# Spine colors (w=top, a=left, s=bottom, d=right)
|
|
1156
1206
|
try:
|
|
1157
1207
|
print("Set spine colors (with matching tick and label colors):")
|
|
1158
|
-
print(" w : top spine | a : left spine")
|
|
1159
|
-
print(" s : bottom spine | d : right spine")
|
|
1160
|
-
print("Example: w:red a:#4561F7 s:blue d:green")
|
|
1208
|
+
print(_colorize_inline_commands(" w : top spine | a : left spine"))
|
|
1209
|
+
print(_colorize_inline_commands(" s : bottom spine | d : right spine"))
|
|
1210
|
+
print(_colorize_inline_commands("Example: w:red a:#4561F7 s:blue d:green"))
|
|
1161
1211
|
line = input("Enter mappings (e.g., w:red a:#4561F7) or q: ").strip()
|
|
1162
1212
|
if line and line.lower() != 'q':
|
|
1163
1213
|
push_state("color-spine")
|
|
@@ -1457,7 +1507,7 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
1457
1507
|
print(f"Existing style files in Styles/:")
|
|
1458
1508
|
for i, f in enumerate(files, 1):
|
|
1459
1509
|
print(f" {i}: {f}")
|
|
1460
|
-
sub = input("Style submenu: (e=export, q=return): ").strip().lower()
|
|
1510
|
+
sub = input(_colorize_prompt("Style submenu: (e=export, q=return): ")).strip().lower()
|
|
1461
1511
|
if sub == 'e':
|
|
1462
1512
|
choice = input("Enter new filename or number to overwrite (q=cancel): ").strip()
|
|
1463
1513
|
if not choice or choice.lower() == 'q':
|
|
@@ -2141,10 +2191,10 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
2141
2191
|
print(f" left a1:{b(wasd['left']['spine'])} a2:{b(wasd['left']['ticks'])} a3:{b(wasd['left']['minor'])} a4:{b(wasd['left']['labels'])} a5:{b(wasd['left']['title'])}")
|
|
2142
2192
|
print(f" right d1:{b(wasd['right']['spine'])} d2:{b(wasd['right']['ticks'])} d3:{b(wasd['right']['minor'])} d4:{b(wasd['right']['labels'])} d5:{b(wasd['right']['title'])}")
|
|
2143
2193
|
|
|
2144
|
-
print("WASD toggles: direction (w/a/s/d) x action (1..5)")
|
|
2145
|
-
print(" 1=spine 2=ticks 3=minor ticks 4=tick labels 5=axis title")
|
|
2146
|
-
print("Examples: 'w2 w5' to toggle top ticks and top title; 'd2 d5' for right.")
|
|
2147
|
-
print("Type 'i' to invert tick direction, 'l' to change tick length, 'list' to show current state, 'q' to go back.")
|
|
2194
|
+
print(_colorize_inline_commands("WASD toggles: direction (w/a/s/d) x action (1..5)"))
|
|
2195
|
+
print(_colorize_inline_commands(" 1=spine 2=ticks 3=minor ticks 4=tick labels 5=axis title"))
|
|
2196
|
+
print(_colorize_inline_commands("Examples: 'w2 w5' to toggle top ticks and top title; 'd2 d5' for right."))
|
|
2197
|
+
print(_colorize_inline_commands("Type 'i' to invert tick direction, 'l' to change tick length, 'list' to show current state, 'q' to go back."))
|
|
2148
2198
|
while True:
|
|
2149
2199
|
cmd = input("t> ").strip().lower()
|
|
2150
2200
|
if not cmd:
|
|
@@ -25,6 +25,52 @@ from .plotting import update_labels as _update_labels
|
|
|
25
25
|
from .utils import _confirm_overwrite
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def _colorize_menu(text):
|
|
29
|
+
"""Colorize menu items: command in cyan, colon in white, description in default."""
|
|
30
|
+
if ':' not in text:
|
|
31
|
+
return text
|
|
32
|
+
parts = text.split(':', 1)
|
|
33
|
+
cmd = parts[0].strip()
|
|
34
|
+
desc = parts[1].strip() if len(parts) > 1 else ''
|
|
35
|
+
return f"\033[96m{cmd}\033[0m: {desc}" # Cyan for command, default for description
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _colorize_prompt(text):
|
|
39
|
+
"""Colorize commands within input prompts. Handles formats like (s=size, f=family, q=return) or (y/n)."""
|
|
40
|
+
import re
|
|
41
|
+
pattern = r'\(([a-z]+=[^,)]+(?:,\s*[a-z]+=[^,)]+)*|[a-z]+(?:/[a-z]+)+)\)'
|
|
42
|
+
|
|
43
|
+
def colorize_match(match):
|
|
44
|
+
content = match.group(1)
|
|
45
|
+
if '/' in content:
|
|
46
|
+
parts = content.split('/')
|
|
47
|
+
colored_parts = [f"\033[96m{p.strip()}\033[0m" for p in parts]
|
|
48
|
+
return f"({'/'.join(colored_parts)})"
|
|
49
|
+
else:
|
|
50
|
+
parts = content.split(',')
|
|
51
|
+
colored_parts = []
|
|
52
|
+
for part in parts:
|
|
53
|
+
part = part.strip()
|
|
54
|
+
if '=' in part:
|
|
55
|
+
cmd, desc = part.split('=', 1)
|
|
56
|
+
colored_parts.append(f"\033[96m{cmd.strip()}\033[0m={desc.strip()}")
|
|
57
|
+
else:
|
|
58
|
+
colored_parts.append(part)
|
|
59
|
+
return f"({', '.join(colored_parts)})"
|
|
60
|
+
|
|
61
|
+
return re.sub(pattern, colorize_match, text)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _colorize_inline_commands(text):
|
|
65
|
+
"""Colorize inline command examples in help text. Colors quoted examples and specific known commands."""
|
|
66
|
+
import re
|
|
67
|
+
# Color quoted command examples (like 's2 w5 a4', 'w2 w5')
|
|
68
|
+
text = re.sub(r"'([a-z0-9\s_-]+)'", lambda m: f"'\033[96m{m.group(1)}\033[0m'", text)
|
|
69
|
+
# Color specific known commands: q, i, l, list, help, all
|
|
70
|
+
text = re.sub(r'\b(q|i|l|list|help|all)\b(?=\s*[=,]|\s*$)', lambda m: f"\033[96m{m.group(1)}\033[0m", text)
|
|
71
|
+
return text
|
|
72
|
+
|
|
73
|
+
|
|
28
74
|
def _print_menu(n_cycles: int, is_dqdv: bool = False):
|
|
29
75
|
# Three-column menu similar to operando: Styles | Geometries | Options
|
|
30
76
|
# Use dynamic column widths for clean alignment.
|
|
@@ -60,13 +106,17 @@ def _print_menu(n_cycles: int, is_dqdv: bool = False):
|
|
|
60
106
|
w2 = max(len("(Geometries)"), *(len(s) for s in col2), 12)
|
|
61
107
|
w3 = max(len("(Options)"), *(len(s) for s in col3), 12)
|
|
62
108
|
rows = max(len(col1), len(col2), len(col3))
|
|
63
|
-
print("\
|
|
64
|
-
print(f" {'(Styles)':<{w1}} {'(Geometries)':<{w2}} {'(Options)':<{w3}}")
|
|
109
|
+
print("\n\033[1mInteractive menu:\033[0m") # Bold title
|
|
110
|
+
print(f" \033[93m{'(Styles)':<{w1}}\033[0m \033[93m{'(Geometries)':<{w2}}\033[0m \033[93m{'(Options)':<{w3}}\033[0m") # Yellow headers
|
|
65
111
|
for i in range(rows):
|
|
66
|
-
p1 = col1[i] if i < len(col1) else ""
|
|
67
|
-
p2 = col2[i] if i < len(col2) else ""
|
|
68
|
-
p3 = col3[i] if i < len(col3) else ""
|
|
69
|
-
|
|
112
|
+
p1 = _colorize_menu(col1[i]) if i < len(col1) else ""
|
|
113
|
+
p2 = _colorize_menu(col2[i]) if i < len(col2) else ""
|
|
114
|
+
p3 = _colorize_menu(col3[i]) if i < len(col3) else ""
|
|
115
|
+
# Add padding to account for ANSI escape codes
|
|
116
|
+
pad1 = w1 + (9 if i < len(col1) else 0)
|
|
117
|
+
pad2 = w2 + (9 if i < len(col2) else 0)
|
|
118
|
+
pad3 = w3 + (9 if i < len(col3) else 0)
|
|
119
|
+
print(f" {p1:<{pad1}} {p2:<{pad2}} {p3:<{pad3}}")
|
|
70
120
|
|
|
71
121
|
|
|
72
122
|
def _iter_cycle_lines(cycle_lines: Dict[int, Dict[str, Optional[object]]]):
|
|
@@ -863,7 +913,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
863
913
|
continue
|
|
864
914
|
if key == 'q':
|
|
865
915
|
try:
|
|
866
|
-
confirm = input("Quit EC interactive? Remember to save (e=export, s=save). Quit now? (y/n): ").strip().lower()
|
|
916
|
+
confirm = input(_colorize_prompt("Quit EC interactive? Remember to save (e=export, s=save). Quit now? (y/n): ")).strip().lower()
|
|
867
917
|
except Exception:
|
|
868
918
|
confirm = 'y'
|
|
869
919
|
if confirm == 'y':
|
|
@@ -1020,7 +1070,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1020
1070
|
xy_in = getattr(fig, '_ec_legend_xy_in', (0.0, 0.0))
|
|
1021
1071
|
print(f"Legend is {'ON' if vis else 'off'}; position (inches from center): x={xy_in[0]:.2f}, y={xy_in[1]:.2f}")
|
|
1022
1072
|
while True:
|
|
1023
|
-
sub = input("Legend: t=toggle, m=set position
|
|
1073
|
+
sub = input(_colorize_prompt("Legend: (t=toggle, m=set position, q=back): ")).strip().lower()
|
|
1024
1074
|
if not sub:
|
|
1025
1075
|
continue
|
|
1026
1076
|
if sub == 'q':
|
|
@@ -1387,13 +1437,15 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1387
1437
|
print(f" Tick widths: xM={x_maj if x_maj is not None else '?'} xm={x_min if x_min is not None else '?'} yM={y_maj if y_maj is not None else '?'} ym={y_min if y_min is not None else '?'}")
|
|
1388
1438
|
if cur_curve_lw is not None:
|
|
1389
1439
|
print(f" Curves (all): {cur_curve_lw:.3g}")
|
|
1390
|
-
print("
|
|
1391
|
-
print(" c : change curve line widths")
|
|
1392
|
-
print(" f : change frame (axes spines) and tick widths")
|
|
1393
|
-
print("
|
|
1394
|
-
print("
|
|
1395
|
-
print("
|
|
1396
|
-
|
|
1440
|
+
print("\033[1mLine submenu:\033[0m")
|
|
1441
|
+
print(f" {_colorize_menu('c : change curve line widths')}")
|
|
1442
|
+
print(f" {_colorize_menu('f : change frame (axes spines) and tick widths')}")
|
|
1443
|
+
print(f" {_colorize_menu('g : toggle grid lines')}")
|
|
1444
|
+
print(f" {_colorize_menu('l : show only lines (no markers) for all curves')}")
|
|
1445
|
+
print(f" {_colorize_menu('ld : show line and dots (markers) for all curves')}")
|
|
1446
|
+
print(f" {_colorize_menu('d : show only dots (no connecting line) for all curves')}")
|
|
1447
|
+
print(f" {_colorize_menu('q : return')}")
|
|
1448
|
+
sub = input(_colorize_prompt("Choose (c/f/g/l/ld/d/q): ")).strip().lower()
|
|
1397
1449
|
if not sub:
|
|
1398
1450
|
continue
|
|
1399
1451
|
if sub == 'q':
|
|
@@ -1451,6 +1503,54 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1451
1503
|
print(f"Set frame width={frame_w}, major tick width={tick_major}, minor tick width={tick_minor}")
|
|
1452
1504
|
except ValueError:
|
|
1453
1505
|
print("Invalid numeric value(s).")
|
|
1506
|
+
elif sub == 'g':
|
|
1507
|
+
push_state("grid")
|
|
1508
|
+
# Toggle grid state - check if any gridlines are visible
|
|
1509
|
+
current_grid = False
|
|
1510
|
+
try:
|
|
1511
|
+
# Check if grid is currently on by looking at gridline visibility
|
|
1512
|
+
for line in ax.get_xgridlines() + ax.get_ygridlines():
|
|
1513
|
+
if line.get_visible():
|
|
1514
|
+
current_grid = True
|
|
1515
|
+
break
|
|
1516
|
+
except Exception:
|
|
1517
|
+
current_grid = ax.xaxis._gridOnMajor if hasattr(ax.xaxis, '_gridOnMajor') else False
|
|
1518
|
+
|
|
1519
|
+
new_grid_state = not current_grid
|
|
1520
|
+
if new_grid_state:
|
|
1521
|
+
# Enable grid with light styling
|
|
1522
|
+
ax.grid(True, color='0.85', linestyle='-', linewidth=0.5, alpha=0.7)
|
|
1523
|
+
else:
|
|
1524
|
+
# Disable grid (no style parameters when disabling)
|
|
1525
|
+
ax.grid(False)
|
|
1526
|
+
fig.canvas.draw()
|
|
1527
|
+
print(f"Grid {'enabled' if new_grid_state else 'disabled'}.")
|
|
1528
|
+
elif sub == 'l':
|
|
1529
|
+
# Line-only mode: set linestyle to solid and remove markers
|
|
1530
|
+
push_state("line-only")
|
|
1531
|
+
for cyc, role, ln in _iter_cycle_lines(cycle_lines):
|
|
1532
|
+
try:
|
|
1533
|
+
# Check if already in line-only mode (has line style and no marker)
|
|
1534
|
+
current_ls = ln.get_linestyle()
|
|
1535
|
+
current_marker = ln.get_marker()
|
|
1536
|
+
# If already line-only (has line, no marker), skip
|
|
1537
|
+
if current_ls not in ['None', '', ' ', 'none'] and current_marker in ['None', '', ' ', 'none', None]:
|
|
1538
|
+
continue
|
|
1539
|
+
# Otherwise, set to line-only
|
|
1540
|
+
ln.set_linestyle('-')
|
|
1541
|
+
ln.set_marker('None')
|
|
1542
|
+
except Exception:
|
|
1543
|
+
pass
|
|
1544
|
+
try:
|
|
1545
|
+
_rebuild_legend(ax)
|
|
1546
|
+
fig.canvas.draw()
|
|
1547
|
+
except Exception:
|
|
1548
|
+
try:
|
|
1549
|
+
_rebuild_legend(ax)
|
|
1550
|
+
except Exception:
|
|
1551
|
+
pass
|
|
1552
|
+
fig.canvas.draw_idle()
|
|
1553
|
+
print("Applied line-only style to all curves.")
|
|
1454
1554
|
elif sub == 'ld':
|
|
1455
1555
|
# Line + dots for all curves
|
|
1456
1556
|
push_state("line+dots")
|
|
@@ -1521,9 +1621,9 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1521
1621
|
# Spine colors (w=top, a=left, s=bottom, d=right)
|
|
1522
1622
|
try:
|
|
1523
1623
|
print("Set spine colors (with matching tick and label colors):")
|
|
1524
|
-
print(" w : top spine | a : left spine")
|
|
1525
|
-
print(" s : bottom spine | d : right spine")
|
|
1526
|
-
print("Example: w:red a:#4561F7 s:blue d:green")
|
|
1624
|
+
print(_colorize_inline_commands(" w : top spine | a : left spine"))
|
|
1625
|
+
print(_colorize_inline_commands(" s : bottom spine | d : right spine"))
|
|
1626
|
+
print(_colorize_inline_commands("Example: w:red a:#4561F7 s:blue d:green"))
|
|
1527
1627
|
line = input("Enter mappings (e.g., w:red a:#4561F7) or q: ").strip()
|
|
1528
1628
|
if line and line.lower() != 'q':
|
|
1529
1629
|
push_state("color-spine")
|
|
@@ -1845,10 +1945,10 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1845
1945
|
elif key == 'c':
|
|
1846
1946
|
print(f"Total cycles: {len(all_cycles)}")
|
|
1847
1947
|
print("Enter one of:")
|
|
1848
|
-
print(" - numbers: e.g. 1 5 10")
|
|
1849
|
-
print(" - mappings: e.g. 1:red 5:#00B006 10:blue")
|
|
1850
|
-
print(" - numbers + palette: e.g. 1 5 10 viridis OR 1 5 10 3")
|
|
1851
|
-
print(" - all (optionally with palette): e.g. all OR all viridis OR all 3")
|
|
1948
|
+
print(_colorize_inline_commands(" - numbers: e.g. 1 5 10"))
|
|
1949
|
+
print(_colorize_inline_commands(" - mappings: e.g. 1:red 5:#00B006 10:blue"))
|
|
1950
|
+
print(_colorize_inline_commands(" - numbers + palette: e.g. 1 5 10 viridis OR 1 5 10 3"))
|
|
1951
|
+
print(_colorize_inline_commands(" - all (optionally with palette): e.g. all OR all viridis OR all 3"))
|
|
1852
1952
|
print("\nRecommended palettes for scientific publications:")
|
|
1853
1953
|
print(" 1. tab10 - Distinct, colorblind-friendly (default matplotlib)")
|
|
1854
1954
|
print(" 2. Set2 - Soft, pastel colors for presentations")
|
|
@@ -99,6 +99,54 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
99
99
|
if not hasattr(fig, '_stack_label_at_bottom'):
|
|
100
100
|
fig._stack_label_at_bottom = False
|
|
101
101
|
|
|
102
|
+
# ANSI color codes for menu highlighting
|
|
103
|
+
def colorize_menu(text):
|
|
104
|
+
"""Colorize menu items: command in cyan, colon in white, description in default."""
|
|
105
|
+
if ':' not in text:
|
|
106
|
+
return text
|
|
107
|
+
parts = text.split(':', 1)
|
|
108
|
+
cmd = parts[0].strip()
|
|
109
|
+
desc = parts[1].strip() if len(parts) > 1 else ''
|
|
110
|
+
return f"\033[96m{cmd}\033[0m: {desc}" # Cyan for command, default for description
|
|
111
|
+
|
|
112
|
+
def colorize_prompt(text):
|
|
113
|
+
"""Colorize commands within input prompts. Handles formats like (s=size, f=family, q=return) or (y/n) or (q=cancel)."""
|
|
114
|
+
import re
|
|
115
|
+
# Pattern to match parenthesized command lists like (s=size, f=family, q=return) or (y/n) or (m/p/s/t/q) or (q=cancel)
|
|
116
|
+
pattern = r'\(([a-z]+=[^,)]+(?:,\s*[a-z]+=[^,)]+)*|[a-z]+(?:/[a-z]+)+)\)'
|
|
117
|
+
|
|
118
|
+
def colorize_match(match):
|
|
119
|
+
content = match.group(1)
|
|
120
|
+
# Check if it's slash-separated (like y/n or m/p/s/t/q)
|
|
121
|
+
if '/' in content:
|
|
122
|
+
parts = content.split('/')
|
|
123
|
+
colored_parts = [f"\033[96m{p.strip()}\033[0m" for p in parts]
|
|
124
|
+
return f"({'/'.join(colored_parts)})"
|
|
125
|
+
# Otherwise it's equals-separated (like s=size, f=family or q=cancel)
|
|
126
|
+
else:
|
|
127
|
+
parts = content.split(',')
|
|
128
|
+
colored_parts = []
|
|
129
|
+
for part in parts:
|
|
130
|
+
part = part.strip()
|
|
131
|
+
if '=' in part:
|
|
132
|
+
cmd, desc = part.split('=', 1)
|
|
133
|
+
colored_parts.append(f"\033[96m{cmd.strip()}\033[0m={desc.strip()}")
|
|
134
|
+
else:
|
|
135
|
+
colored_parts.append(part)
|
|
136
|
+
return f"({', '.join(colored_parts)})"
|
|
137
|
+
|
|
138
|
+
return re.sub(pattern, colorize_match, text)
|
|
139
|
+
|
|
140
|
+
def colorize_inline_commands(text):
|
|
141
|
+
"""Colorize inline command examples in help text. Colors quoted examples and specific known commands."""
|
|
142
|
+
import re
|
|
143
|
+
# Color quoted command examples (like 's2 w5 a4', 'w2 w5', or 'all magma_r')
|
|
144
|
+
text = re.sub(r"'([a-z0-9\s_-]+)'", lambda m: f"'\033[96m{m.group(1)}\033[0m'", text)
|
|
145
|
+
# Color specific known single-letter commands: q, i, l, when they appear as standalone commands
|
|
146
|
+
# Pattern: word boundary + (q|i|l|list|help|all) + space/equals/comma/end
|
|
147
|
+
text = re.sub(r'\b(q|i|l|list|help|all)\b(?=\s*[=,]|\s*$)', lambda m: f"\033[96m{m.group(1)}\033[0m", text)
|
|
148
|
+
return text
|
|
149
|
+
|
|
102
150
|
# REPLACED print_main_menu with column layout (now hides 'd' and 'y' in --stack)
|
|
103
151
|
is_diffraction = use_Q or (not use_r and not use_E and not use_k and not use_rft) # 2θ or Q
|
|
104
152
|
def print_main_menu():
|
|
@@ -121,18 +169,23 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
121
169
|
|
|
122
170
|
if not is_diffraction:
|
|
123
171
|
col3 = [item for item in col3 if not item.startswith("n:")]
|
|
124
|
-
# Dynamic widths for cleaner alignment across terminals
|
|
172
|
+
# Dynamic widths for cleaner alignment across terminals (account for ANSI codes)
|
|
173
|
+
# Use plain text length for width calculations
|
|
125
174
|
w1 = max(len("(Styles)"), *(len(s) for s in col1), 16)
|
|
126
175
|
w2 = max(len("(Geometries)"), *(len(s) for s in col2), 16)
|
|
127
176
|
w3 = max(len("(Options)"), *(len(s) for s in col3), 16)
|
|
128
177
|
rows = max(len(col1), len(col2), len(col3))
|
|
129
|
-
print("\
|
|
130
|
-
print(f" {'(Styles)':<{w1}} {'(Geometries)':<{w2}} {'(Options)':<{w3}}")
|
|
178
|
+
print("\n\033[1mInteractive menu:\033[0m") # Bold title
|
|
179
|
+
print(f" \033[93m{'(Styles)':<{w1}}\033[0m \033[93m{'(Geometries)':<{w2}}\033[0m \033[93m{'(Options)':<{w3}}\033[0m") # Yellow headers
|
|
131
180
|
for i in range(rows):
|
|
132
|
-
p1 = col1[i] if i < len(col1) else ""
|
|
133
|
-
p2 = col2[i] if i < len(col2) else ""
|
|
134
|
-
p3 = col3[i] if i < len(col3) else ""
|
|
135
|
-
|
|
181
|
+
p1 = colorize_menu(col1[i]) if i < len(col1) else ""
|
|
182
|
+
p2 = colorize_menu(col2[i]) if i < len(col2) else ""
|
|
183
|
+
p3 = colorize_menu(col3[i]) if i < len(col3) else ""
|
|
184
|
+
# Add padding to account for ANSI escape codes (9 chars per colorized item)
|
|
185
|
+
pad1 = w1 + (9 if i < len(col1) else 0)
|
|
186
|
+
pad2 = w2 + (9 if i < len(col2) else 0)
|
|
187
|
+
pad3 = w3 + (9 if i < len(col3) else 0)
|
|
188
|
+
print(f" {p1:<{pad1}} {p2:<{pad2}} {p3:<{pad3}}")
|
|
136
189
|
|
|
137
190
|
# --- Helper for spine visibility ---
|
|
138
191
|
def set_spine_visible(which, visible):
|
|
@@ -690,7 +743,8 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
690
743
|
"show_cif_hkl": (bool(getattr(_bp, 'show_cif_hkl')) if _bp is not None and hasattr(_bp, 'show_cif_hkl') else False),
|
|
691
744
|
"show_cif_titles": (bool(getattr(_bp, 'show_cif_titles')) if _bp is not None and hasattr(_bp, 'show_cif_titles') else True),
|
|
692
745
|
"rotation_angle": getattr(ax, '_rotation_angle', 0),
|
|
693
|
-
"stack_label_at_bottom": getattr(fig, '_stack_label_at_bottom', False)
|
|
746
|
+
"stack_label_at_bottom": getattr(fig, '_stack_label_at_bottom', False),
|
|
747
|
+
"grid": ax.xaxis._gridOnMajor if hasattr(ax.xaxis, '_gridOnMajor') else False
|
|
694
748
|
}
|
|
695
749
|
# Line + data arrays
|
|
696
750
|
for i, ln in enumerate(ax.lines):
|
|
@@ -906,6 +960,16 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
906
960
|
if 'stack_label_at_bottom' in snap:
|
|
907
961
|
fig._stack_label_at_bottom = bool(snap['stack_label_at_bottom'])
|
|
908
962
|
|
|
963
|
+
# Restore grid state
|
|
964
|
+
if 'grid' in snap:
|
|
965
|
+
try:
|
|
966
|
+
if snap['grid']:
|
|
967
|
+
ax.grid(True, color='0.85', linestyle='-', linewidth=0.5, alpha=0.7)
|
|
968
|
+
else:
|
|
969
|
+
ax.grid(False)
|
|
970
|
+
except Exception:
|
|
971
|
+
pass
|
|
972
|
+
|
|
909
973
|
# CIF tick sets & label visibility (write back to batplot module globals)
|
|
910
974
|
if _bp is not None and snap.get("cif_tick_series") is not None and hasattr(_bp, 'cif_tick_series'):
|
|
911
975
|
try:
|
|
@@ -960,7 +1024,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
960
1024
|
continue
|
|
961
1025
|
|
|
962
1026
|
if key == 'q':
|
|
963
|
-
confirm = input("Quit interactive? Remember to save (e=export, s=save). Quit now? (y/n): ").strip().lower()
|
|
1027
|
+
confirm = input(colorize_prompt("Quit interactive? Remember to save (e=export, s=save). Quit now? (y/n): ")).strip().lower()
|
|
964
1028
|
if confirm == 'y':
|
|
965
1029
|
break
|
|
966
1030
|
else:
|
|
@@ -995,11 +1059,11 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
995
1059
|
elif key == 'h': # legend submenu
|
|
996
1060
|
try:
|
|
997
1061
|
while True:
|
|
998
|
-
print("\
|
|
999
|
-
print(" v: show/hide curve names")
|
|
1062
|
+
print("\n\033[1mLegend submenu:\033[0m")
|
|
1063
|
+
print(f" {colorize_menu('v: show/hide curve names')}")
|
|
1000
1064
|
current_pos = "bottom-right" if getattr(fig, '_stack_label_at_bottom', False) else "top-right"
|
|
1001
|
-
print(f" s: legend position (current: {current_pos})")
|
|
1002
|
-
print(" q: back to main menu")
|
|
1065
|
+
print(f" {colorize_menu(f's: legend position (current: {current_pos})')}")
|
|
1066
|
+
print(f" {colorize_menu('q: back to main menu')}")
|
|
1003
1067
|
sub_key = input("Choose: ").strip().lower()
|
|
1004
1068
|
|
|
1005
1069
|
if sub_key == 'q':
|
|
@@ -1136,14 +1200,14 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1136
1200
|
except Exception:
|
|
1137
1201
|
pass
|
|
1138
1202
|
while True:
|
|
1139
|
-
print("
|
|
1140
|
-
print(" m : manual color mapping (e.g., 1:red 2:#00B006)")
|
|
1141
|
-
print(" p : apply colormap palette to a range (e.g., 1-3 viridis)")
|
|
1142
|
-
print(" s : spine colors (e.g., w:red a:#4561F7 for top & left)")
|
|
1203
|
+
print("\033[1mColor menu:\033[0m")
|
|
1204
|
+
print(f" {colorize_menu('m : manual color mapping (e.g., 1:red 2:#00B006)')}")
|
|
1205
|
+
print(f" {colorize_menu('p : apply colormap palette to a range (e.g., 1-3 viridis)')}")
|
|
1206
|
+
print(f" {colorize_menu('s : spine colors (e.g., w:red a:#4561F7 for top & left)')}")
|
|
1143
1207
|
if has_cif and (_bp is not None and getattr(_bp, 'cif_tick_series', None)):
|
|
1144
|
-
print(" t : change CIF tick set color (e.g., 1:red 2:#888888)")
|
|
1145
|
-
print(" q : return to main menu")
|
|
1146
|
-
sub = input("Choose (m/p/s/t/q): ").strip().lower()
|
|
1208
|
+
print(f" {colorize_menu('t : change CIF tick set color (e.g., 1:red 2:#888888)')}")
|
|
1209
|
+
print(f" {colorize_menu('q : return to main menu')}")
|
|
1210
|
+
sub = input(colorize_prompt("Choose (m/p/s/t/q): ")).strip().lower()
|
|
1147
1211
|
if sub == 'q':
|
|
1148
1212
|
break
|
|
1149
1213
|
if sub == '':
|
|
@@ -1177,9 +1241,9 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1177
1241
|
elif sub == 's':
|
|
1178
1242
|
# Spine colors (w=top, a=left, s=bottom, d=right)
|
|
1179
1243
|
print("Set spine colors (with matching tick and label colors):")
|
|
1180
|
-
print(" w : top spine | a : left spine")
|
|
1181
|
-
print(" s : bottom spine | d : right spine")
|
|
1182
|
-
print("Example: w:red a:#4561F7 s:blue d:green")
|
|
1244
|
+
print(colorize_inline_commands(" w : top spine | a : left spine"))
|
|
1245
|
+
print(colorize_inline_commands(" s : bottom spine | d : right spine"))
|
|
1246
|
+
print(colorize_inline_commands("Example: w:red a:#4561F7 s:blue d:green"))
|
|
1183
1247
|
line = input("Enter mappings (e.g., w:red a:#4561F7) or q: ").strip()
|
|
1184
1248
|
if not line or line.lower() == 'q':
|
|
1185
1249
|
print("Canceled.")
|
|
@@ -1252,7 +1316,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1252
1316
|
extras.append('batlowK')
|
|
1253
1317
|
print("Common perceptually uniform palettes:")
|
|
1254
1318
|
print(" " + ", ".join(base_palettes + extras[:2]))
|
|
1255
|
-
print("Example: 1-4 viridis or: all magma_r or: 1-3,5 plasma, _r for reverse")
|
|
1319
|
+
print(colorize_inline_commands("Example: 1-4 viridis or: all magma_r or: 1-3,5 plasma, _r for reverse"))
|
|
1256
1320
|
line = input("Enter range(s) and palette (e.g., '1-3 viridis') or q: ").strip()
|
|
1257
1321
|
if not line or line.lower() == 'q':
|
|
1258
1322
|
print("Canceled.")
|
|
@@ -1663,12 +1727,12 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1663
1727
|
except Exception as e:
|
|
1664
1728
|
print(f"Error setting Y-axis range: {e}")
|
|
1665
1729
|
elif key == 'd': # <-- DELTA / OFFSET HANDLER (now only reachable if not args.stack)
|
|
1666
|
-
print("\
|
|
1667
|
-
print(" 1-{}: adjust individual curve offset
|
|
1668
|
-
print(" a: adjust total offset (baseline shift)")
|
|
1669
|
-
print(" r: reset all offsets to 0")
|
|
1670
|
-
print(" d: change delta spacing (original behavior)")
|
|
1671
|
-
print(" q: back to main menu")
|
|
1730
|
+
print("\n\033[1mOffset adjustment menu:\033[0m")
|
|
1731
|
+
print(f" {colorize_menu('1-{}: adjust individual curve offset'.format(len(labels)))}")
|
|
1732
|
+
print(f" {colorize_menu('a: adjust total offset (baseline shift)')}")
|
|
1733
|
+
print(f" {colorize_menu('r: reset all offsets to 0')}")
|
|
1734
|
+
print(f" {colorize_menu('d: change delta spacing (original behavior)')}")
|
|
1735
|
+
print(f" {colorize_menu('q: back to main menu')}")
|
|
1672
1736
|
|
|
1673
1737
|
while True:
|
|
1674
1738
|
offset_cmd = input("Offset> ").strip().lower()
|
|
@@ -1913,13 +1977,15 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1913
1977
|
elif key == 'l':
|
|
1914
1978
|
try:
|
|
1915
1979
|
while True:
|
|
1916
|
-
print("
|
|
1917
|
-
print(" c : change curve line widths")
|
|
1918
|
-
print(" f : change frame (axes spines) and tick widths")
|
|
1919
|
-
print("
|
|
1920
|
-
print("
|
|
1921
|
-
print("
|
|
1922
|
-
|
|
1980
|
+
print("\033[1mLine submenu:\033[0m")
|
|
1981
|
+
print(f" {colorize_menu('c : change curve line widths')}")
|
|
1982
|
+
print(f" {colorize_menu('f : change frame (axes spines) and tick widths')}")
|
|
1983
|
+
print(f" {colorize_menu('g : toggle grid lines')}")
|
|
1984
|
+
print(f" {colorize_menu('l : show only lines (no markers) for all curves')}")
|
|
1985
|
+
print(f" {colorize_menu('ld : show line and dots (markers) for all curves')}")
|
|
1986
|
+
print(f" {colorize_menu('d : show only dots (no connecting line) for all curves')}")
|
|
1987
|
+
print(f" {colorize_menu('q : return')}")
|
|
1988
|
+
sub = input(colorize_prompt("Choose (c/f/g/l/ld/d/q): ")).strip().lower()
|
|
1923
1989
|
if sub == 'q':
|
|
1924
1990
|
break
|
|
1925
1991
|
if sub == '':
|
|
@@ -1978,6 +2044,43 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1978
2044
|
print(f"Set frame width={frame_w}, major tick width={tick_major}, minor tick width={tick_minor}")
|
|
1979
2045
|
except ValueError:
|
|
1980
2046
|
print("Invalid numeric value(s).")
|
|
2047
|
+
elif sub == 'g':
|
|
2048
|
+
push_state("grid")
|
|
2049
|
+
# Toggle grid state - check if any gridlines are visible
|
|
2050
|
+
current_grid = False
|
|
2051
|
+
try:
|
|
2052
|
+
# Check if grid is currently on by looking at gridline visibility
|
|
2053
|
+
for line in ax.get_xgridlines() + ax.get_ygridlines():
|
|
2054
|
+
if line.get_visible():
|
|
2055
|
+
current_grid = True
|
|
2056
|
+
break
|
|
2057
|
+
except Exception:
|
|
2058
|
+
current_grid = ax.xaxis._gridOnMajor if hasattr(ax.xaxis, '_gridOnMajor') else False
|
|
2059
|
+
|
|
2060
|
+
new_grid_state = not current_grid
|
|
2061
|
+
if new_grid_state:
|
|
2062
|
+
# Enable grid with light styling
|
|
2063
|
+
ax.grid(True, color='0.85', linestyle='-', linewidth=0.5, alpha=0.7)
|
|
2064
|
+
else:
|
|
2065
|
+
# Disable grid (no style parameters when disabling)
|
|
2066
|
+
ax.grid(False)
|
|
2067
|
+
fig.canvas.draw()
|
|
2068
|
+
print(f"Grid {'enabled' if new_grid_state else 'disabled'}.")
|
|
2069
|
+
elif sub == 'l':
|
|
2070
|
+
# Line-only mode: set linestyle to solid and remove markers
|
|
2071
|
+
push_state("line-only")
|
|
2072
|
+
for ln in ax.lines:
|
|
2073
|
+
# Check if already in line-only mode (has line style and no marker)
|
|
2074
|
+
current_ls = ln.get_linestyle()
|
|
2075
|
+
current_marker = ln.get_marker()
|
|
2076
|
+
# If already line-only (has line, no marker), skip
|
|
2077
|
+
if current_ls not in ['None', '', ' ', 'none'] and current_marker in ['None', '', ' ', 'none', None]:
|
|
2078
|
+
continue
|
|
2079
|
+
# Otherwise, set to line-only
|
|
2080
|
+
ln.set_linestyle('-')
|
|
2081
|
+
ln.set_marker('None')
|
|
2082
|
+
fig.canvas.draw()
|
|
2083
|
+
print("Applied line-only style to all curves.")
|
|
1981
2084
|
elif sub == 'ld':
|
|
1982
2085
|
push_state("line+dots")
|
|
1983
2086
|
try:
|
|
@@ -2026,7 +2129,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2026
2129
|
print(f"Error setting widths: {e}")
|
|
2027
2130
|
elif key == 'f':
|
|
2028
2131
|
while True:
|
|
2029
|
-
subkey = input("Font submenu (s=size, f=family, q=return): ").strip().lower()
|
|
2132
|
+
subkey = input(colorize_prompt("Font submenu (s=size, f=family, q=return): ")).strip().lower()
|
|
2030
2133
|
if subkey == 'q':
|
|
2031
2134
|
break
|
|
2032
2135
|
if subkey == '':
|
|
@@ -2080,7 +2183,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2080
2183
|
elif key == 'g':
|
|
2081
2184
|
try:
|
|
2082
2185
|
while True:
|
|
2083
|
-
choice = input("Resize submenu: (p=plot frame, c=canvas, q=cancel): ").strip().lower()
|
|
2186
|
+
choice = input(colorize_prompt("Resize submenu: (p=plot frame, c=canvas, q=cancel): ")).strip().lower()
|
|
2084
2187
|
if not choice:
|
|
2085
2188
|
continue
|
|
2086
2189
|
if choice == 'q':
|
|
@@ -2157,11 +2260,11 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2157
2260
|
elif key == 't':
|
|
2158
2261
|
try:
|
|
2159
2262
|
while True:
|
|
2160
|
-
print("
|
|
2161
|
-
print(" wasd choose side: w=top, a=left, s=bottom, d=right")
|
|
2162
|
-
print(" 1..5 choose what: 1=spine line, 2=major ticks, 3=minor ticks, 4=labels, 5=axis title")
|
|
2163
|
-
print(" Combine letter+number to toggle, e.g. 's2 w5 a4' (case-insensitive)")
|
|
2164
|
-
print(" i = invert tick direction, l = change tick length, list = show state, q = return")
|
|
2263
|
+
print("\033[1mToggle help:\033[0m")
|
|
2264
|
+
print(colorize_inline_commands(" wasd choose side: w=top, a=left, s=bottom, d=right"))
|
|
2265
|
+
print(colorize_inline_commands(" 1..5 choose what: 1=spine line, 2=major ticks, 3=minor ticks, 4=labels, 5=axis title"))
|
|
2266
|
+
print(colorize_inline_commands(" Combine letter+number to toggle, e.g. 's2 w5 a4' (case-insensitive)"))
|
|
2267
|
+
print(colorize_inline_commands(" i = invert tick direction, l = change tick length, list = show state, q = return"))
|
|
2165
2268
|
cmd = input("Enter code(s): ").strip().lower()
|
|
2166
2269
|
if not cmd:
|
|
2167
2270
|
continue
|
|
@@ -2464,7 +2567,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2464
2567
|
print("Existing style files in Styles/ (.bps/.bpsg):")
|
|
2465
2568
|
for _i, _f in enumerate(_bpcfg_files, 1):
|
|
2466
2569
|
print(f" {_i}: {_f}")
|
|
2467
|
-
sub = input("Style submenu: (e=export, q=return, r=refresh): ").strip().lower()
|
|
2570
|
+
sub = input(colorize_prompt("Style submenu: (e=export, q=return, r=refresh): ")).strip().lower()
|
|
2468
2571
|
if sub == 'q':
|
|
2469
2572
|
break
|
|
2470
2573
|
if sub == 'r' or sub == '':
|
|
@@ -28,6 +28,53 @@ from .ui import position_right_ylabel as _ui_position_right_ylabel
|
|
|
28
28
|
from .ui import position_bottom_xlabel as _ui_position_bottom_xlabel
|
|
29
29
|
from .ui import position_left_ylabel as _ui_position_left_ylabel
|
|
30
30
|
|
|
31
|
+
|
|
32
|
+
def _colorize_menu(text):
|
|
33
|
+
"""Colorize menu items: command in cyan, colon in white, description in default."""
|
|
34
|
+
if ':' not in text:
|
|
35
|
+
return text
|
|
36
|
+
parts = text.split(':', 1)
|
|
37
|
+
cmd = parts[0].strip()
|
|
38
|
+
desc = parts[1].strip() if len(parts) > 1 else ''
|
|
39
|
+
return f"\033[96m{cmd}\033[0m: {desc}" # Cyan for command, default for description
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _colorize_prompt(text):
|
|
43
|
+
"""Colorize commands within input prompts. Handles formats like (s=size, f=family, q=return) or (y/n)."""
|
|
44
|
+
import re
|
|
45
|
+
pattern = r'\(([a-z]+=[^,)]+(?:,\s*[a-z]+=[^,)]+)*|[a-z]+(?:/[a-z]+)+)\)'
|
|
46
|
+
|
|
47
|
+
def colorize_match(match):
|
|
48
|
+
content = match.group(1)
|
|
49
|
+
if '/' in content:
|
|
50
|
+
parts = content.split('/')
|
|
51
|
+
colored_parts = [f"\033[96m{p.strip()}\033[0m" for p in parts]
|
|
52
|
+
return f"({'/'.join(colored_parts)})"
|
|
53
|
+
else:
|
|
54
|
+
parts = content.split(',')
|
|
55
|
+
colored_parts = []
|
|
56
|
+
for part in parts:
|
|
57
|
+
part = part.strip()
|
|
58
|
+
if '=' in part:
|
|
59
|
+
cmd, desc = part.split('=', 1)
|
|
60
|
+
colored_parts.append(f"\033[96m{cmd.strip()}\033[0m={desc.strip()}")
|
|
61
|
+
else:
|
|
62
|
+
colored_parts.append(part)
|
|
63
|
+
return f"({', '.join(colored_parts)})"
|
|
64
|
+
|
|
65
|
+
return re.sub(pattern, colorize_match, text)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _colorize_inline_commands(text):
|
|
69
|
+
"""Colorize inline command examples in help text. Colors quoted examples and specific known commands."""
|
|
70
|
+
import re
|
|
71
|
+
# Color quoted command examples (like 's2 w5 a4', 'w2 w5')
|
|
72
|
+
text = re.sub(r"'([a-z0-9\s_-]+)'", lambda m: f"'\033[96m{m.group(1)}\033[0m'", text)
|
|
73
|
+
# Color specific known commands: q, i, l, list, help, all
|
|
74
|
+
text = re.sub(r'\b(q|i|l|list|help|all)\b(?=\s*[=,]|\s*$)', lambda m: f"\033[96m{m.group(1)}\033[0m", text)
|
|
75
|
+
return text
|
|
76
|
+
|
|
77
|
+
|
|
31
78
|
# ============================================================================
|
|
32
79
|
# Constants
|
|
33
80
|
# ============================================================================
|
|
@@ -375,14 +422,19 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax):
|
|
|
375
422
|
w3 = max(len("(EC)"), *(len(s) for s in col3), 14)
|
|
376
423
|
w4 = max(len("(Options)"), *(len(s) for s in col4), 16)
|
|
377
424
|
rows = max(len(col1), len(col2), len(col3), len(col4))
|
|
378
|
-
print("\
|
|
379
|
-
print(f" {'(Styles)':<{w1}} {'(Operando)':<{w2}} {'(EC)':<{w3}} {'(Options)':<{w4}}")
|
|
425
|
+
print("\n\033[1mInteractive menu:\033[0m") # Bold title
|
|
426
|
+
print(f" \033[93m{'(Styles)':<{w1}}\033[0m \033[93m{'(Operando)':<{w2}}\033[0m \033[93m{'(EC)':<{w3}}\033[0m \033[93m{'(Options)':<{w4}}\033[0m") # Yellow headers
|
|
380
427
|
for i in range(rows):
|
|
381
|
-
p1 = col1[i] if i < len(col1) else ""
|
|
382
|
-
p2 = col2[i] if i < len(col2) else ""
|
|
383
|
-
p3 = col3[i] if i < len(col3) else ""
|
|
384
|
-
p4 = col4[i] if i < len(col4) else ""
|
|
385
|
-
|
|
428
|
+
p1 = _colorize_menu(col1[i]) if i < len(col1) else ""
|
|
429
|
+
p2 = _colorize_menu(col2[i]) if i < len(col2) else ""
|
|
430
|
+
p3 = _colorize_menu(col3[i]) if i < len(col3) else ""
|
|
431
|
+
p4 = _colorize_menu(col4[i]) if i < len(col4) else ""
|
|
432
|
+
# Add padding to account for ANSI escape codes
|
|
433
|
+
pad1 = w1 + (9 if i < len(col1) else 0)
|
|
434
|
+
pad2 = w2 + (9 if i < len(col2) else 0)
|
|
435
|
+
pad3 = w3 + (9 if i < len(col3) else 0)
|
|
436
|
+
pad4 = w4 + (9 if i < len(col4) else 0)
|
|
437
|
+
print(f" {p1:<{pad1}} {p2:<{pad2}} {p3:<{pad3}} {p4:<{pad4}}")
|
|
386
438
|
else:
|
|
387
439
|
# Operando-only menu (no EC panel)
|
|
388
440
|
col1 = [
|
|
@@ -415,13 +467,17 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax):
|
|
|
415
467
|
w2 = max(len("(Operando)"), *(len(s) for s in col2), 14)
|
|
416
468
|
w3 = max(len("(Options)"), *(len(s) for s in col3), 16)
|
|
417
469
|
rows = max(len(col1), len(col2), len(col3))
|
|
418
|
-
print("\
|
|
419
|
-
print(f" {'(Styles)':<{w1}} {'(Operando)':<{w2}} {'(Options)':<{w3}}")
|
|
470
|
+
print("\n\033[1mInteractive menu:\033[0m") # Bold title
|
|
471
|
+
print(f" \033[93m{'(Styles)':<{w1}}\033[0m \033[93m{'(Operando)':<{w2}}\033[0m \033[93m{'(Options)':<{w3}}\033[0m") # Yellow headers
|
|
420
472
|
for i in range(rows):
|
|
421
|
-
p1 = col1[i] if i < len(col1) else ""
|
|
422
|
-
p2 = col2[i] if i < len(col2) else ""
|
|
423
|
-
p3 = col3[i] if i < len(col3) else ""
|
|
424
|
-
|
|
473
|
+
p1 = _colorize_menu(col1[i]) if i < len(col1) else ""
|
|
474
|
+
p2 = _colorize_menu(col2[i]) if i < len(col2) else ""
|
|
475
|
+
p3 = _colorize_menu(col3[i]) if i < len(col3) else ""
|
|
476
|
+
# Add padding to account for ANSI escape codes
|
|
477
|
+
pad1 = w1 + (9 if i < len(col1) else 0)
|
|
478
|
+
pad2 = w2 + (9 if i < len(col2) else 0)
|
|
479
|
+
pad3 = w3 + (9 if i < len(col3) else 0)
|
|
480
|
+
print(f" {p1:<{pad1}} {p2:<{pad2}} {p3:<{pad3}}")
|
|
425
481
|
print()
|
|
426
482
|
def print_menu_old():
|
|
427
483
|
col1 = [
|
|
@@ -1615,11 +1671,11 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax):
|
|
|
1615
1671
|
elif cmd == 'l':
|
|
1616
1672
|
# Line widths submenu for both operando and EC panes
|
|
1617
1673
|
print("Line widths: set frame (spines) and tick widths for both operando and EC")
|
|
1618
|
-
print("Enter frame/tick width (e.g., '1.5' or 'f t' for frame/tick separately)")
|
|
1674
|
+
print(_colorize_inline_commands("Enter frame/tick width (e.g., '1.5' or 'f t' for frame/tick separately)"))
|
|
1619
1675
|
print("Format examples:")
|
|
1620
|
-
print(" 1.5 - set both frame and ticks to 1.5")
|
|
1621
|
-
print(" 1.5 2.5 - set frame=1.5, ticks=2.5")
|
|
1622
|
-
print(" q - cancel")
|
|
1676
|
+
print(_colorize_inline_commands(" 1.5 - set both frame and ticks to 1.5"))
|
|
1677
|
+
print(_colorize_inline_commands(" 1.5 2.5 - set frame=1.5, ticks=2.5"))
|
|
1678
|
+
print(_colorize_inline_commands(" q - cancel"))
|
|
1623
1679
|
|
|
1624
1680
|
inp = input("Line widths> ").strip().lower()
|
|
1625
1681
|
if not inp or inp == 'q':
|
|
@@ -1756,9 +1812,9 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax):
|
|
|
1756
1812
|
pass
|
|
1757
1813
|
while True:
|
|
1758
1814
|
if ec_ax is not None:
|
|
1759
|
-
print("Choose pane: o=operando, e=ec, q=back")
|
|
1815
|
+
print(_colorize_inline_commands("Choose pane: o=operando, e=ec, q=back"))
|
|
1760
1816
|
else:
|
|
1761
|
-
print("Choose pane: o=operando, q=back")
|
|
1817
|
+
print(_colorize_inline_commands("Choose pane: o=operando, q=back"))
|
|
1762
1818
|
pane = input("ot> ").strip().lower()
|
|
1763
1819
|
if not pane:
|
|
1764
1820
|
continue
|
|
@@ -2288,7 +2344,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax):
|
|
|
2288
2344
|
print(" 4. cividis - Perceptually uniform, optimized for color vision deficiency")
|
|
2289
2345
|
print(" 5. magma - Perceptually uniform (black→white), excellent for grayscale")
|
|
2290
2346
|
print("\nOther available: " + ", ".join(base + extras))
|
|
2291
|
-
print("Append _r to reverse (e.g., viridis_r or 1_r). Blank to cancel.")
|
|
2347
|
+
print(_colorize_inline_commands("Append _r to reverse (e.g., viridis_r or 1_r). Blank to cancel."))
|
|
2292
2348
|
choice = input("Palette name or number (1-5): ").strip()
|
|
2293
2349
|
if not choice:
|
|
2294
2350
|
print_menu(); continue
|
|
@@ -231,6 +231,8 @@ def dump_session(
|
|
|
231
231
|
sess['curve_names_visible'] = bool(getattr(fig, '_curve_names_visible', True))
|
|
232
232
|
# Save stack label position preference
|
|
233
233
|
sess['stack_label_at_bottom'] = bool(getattr(fig, '_stack_label_at_bottom', False))
|
|
234
|
+
# Save grid state
|
|
235
|
+
sess['grid'] = ax.xaxis._gridOnMajor if hasattr(ax.xaxis, '_gridOnMajor') else False
|
|
234
236
|
if skip_confirm:
|
|
235
237
|
target = filename
|
|
236
238
|
else:
|
|
@@ -286,6 +286,7 @@ def export_style_config(
|
|
|
286
286
|
"x": ax.xaxis.label.get_color(),
|
|
287
287
|
"y": ax.yaxis.label.get_color(),
|
|
288
288
|
},
|
|
289
|
+
"grid": ax.xaxis._gridOnMajor if hasattr(ax.xaxis, '_gridOnMajor') else False,
|
|
289
290
|
"lines": [
|
|
290
291
|
{
|
|
291
292
|
"index": i,
|
|
@@ -777,6 +778,16 @@ def apply_style_config(
|
|
|
777
778
|
except Exception as e:
|
|
778
779
|
print(f"Warning: Could not restore rotation angle: {e}")
|
|
779
780
|
|
|
781
|
+
# Restore grid state
|
|
782
|
+
if "grid" in cfg:
|
|
783
|
+
try:
|
|
784
|
+
if bool(cfg["grid"]):
|
|
785
|
+
ax.grid(True, color='0.85', linestyle='-', linewidth=0.5, alpha=0.7)
|
|
786
|
+
else:
|
|
787
|
+
ax.grid(False)
|
|
788
|
+
except Exception as e:
|
|
789
|
+
print(f"Warning: Could not restore grid state: {e}")
|
|
790
|
+
|
|
780
791
|
# Re-run label placement with current mode (no mode changes via Styles)
|
|
781
792
|
stack_label_bottom = getattr(fig, '_stack_label_at_bottom', False)
|
|
782
793
|
update_labels_func(ax, y_data_list, label_text_objects, args.stack, stack_label_bottom)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "batplot"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.2"
|
|
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
|