batplot 1.3.3__tar.gz → 1.3.5__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.3.3 → batplot-1.3.5}/PKG-INFO +10 -3
- {batplot-1.3.3 → batplot-1.3.5}/README.md +9 -2
- {batplot-1.3.3 → batplot-1.3.5}/batplot/args.py +17 -15
- {batplot-1.3.3 → batplot-1.3.5}/batplot/batch.py +5 -5
- {batplot-1.3.3 → batplot-1.3.5}/batplot/batplot.py +91 -56
- {batplot-1.3.3 → batplot-1.3.5}/batplot/cpc_interactive.py +52 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot/electrochem_interactive.py +138 -1
- {batplot-1.3.3 → batplot-1.3.5}/batplot/interactive.py +304 -36
- {batplot-1.3.3 → batplot-1.3.5}/batplot/operando.py +5 -14
- {batplot-1.3.3 → batplot-1.3.5}/batplot/operando_ec_interactive.py +503 -196
- {batplot-1.3.3 → batplot-1.3.5}/batplot/readers.py +187 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot/session.py +9 -1
- {batplot-1.3.3 → batplot-1.3.5}/batplot/style.py +80 -1
- {batplot-1.3.3 → batplot-1.3.5}/batplot.egg-info/PKG-INFO +10 -3
- {batplot-1.3.3 → batplot-1.3.5}/pyproject.toml +1 -1
- {batplot-1.3.3 → batplot-1.3.5}/LICENSE +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot/__init__.py +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot/batplot_new.py +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot/cif.py +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot/cli.py +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot/converters.py +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot/modes.py +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot/plotting.py +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot/ui.py +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot/utils.py +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot.egg-info/SOURCES.txt +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot.egg-info/dependency_links.txt +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot.egg-info/entry_points.txt +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot.egg-info/requires.txt +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/batplot.egg-info/top_level.txt +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/setup.cfg +0 -0
- {batplot-1.3.3 → batplot-1.3.5}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: batplot
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.5
|
|
4
4
|
Summary: Interactive plotting for XRD, PDF, and XAS data (.xye, .xy, .qye, .dat, .csv, .gr, .nor, .chik, .chir)
|
|
5
5
|
Author-email: Tian Dai <tianda@uio.no>
|
|
6
6
|
License: MIT License
|
|
@@ -91,8 +91,11 @@ batplot --all
|
|
|
91
91
|
# Batch mode with options: custom axis and range
|
|
92
92
|
batplot --all --xaxis 2theta --xrange 10 80
|
|
93
93
|
|
|
94
|
-
#
|
|
95
|
-
batplot
|
|
94
|
+
# Normalize data (--stack mode auto-normalizes by default)
|
|
95
|
+
batplot allfiles --norm
|
|
96
|
+
|
|
97
|
+
# Batch mode: convert 2theta to Q
|
|
98
|
+
batplot --all --wl 1.5406
|
|
96
99
|
```
|
|
97
100
|
|
|
98
101
|
### Electrochemistry
|
|
@@ -132,6 +135,10 @@ batplot --all geom.bpsg --cpc --mass 5.4 # Apply style+geometry to all CPC fi
|
|
|
132
135
|
# Correlate in-situ XRD with electrochemistry
|
|
133
136
|
# (Place both .xye and .mpt files in same directory)
|
|
134
137
|
batplot --operando --interactive
|
|
138
|
+
|
|
139
|
+
# Operando mode without electrochemistry data
|
|
140
|
+
# (Only .xye files, no .mpt file)
|
|
141
|
+
batplot --operando --interactive
|
|
135
142
|
```
|
|
136
143
|
|
|
137
144
|
## Supported File Formats
|
|
@@ -42,8 +42,11 @@ batplot --all
|
|
|
42
42
|
# Batch mode with options: custom axis and range
|
|
43
43
|
batplot --all --xaxis 2theta --xrange 10 80
|
|
44
44
|
|
|
45
|
-
#
|
|
46
|
-
batplot
|
|
45
|
+
# Normalize data (--stack mode auto-normalizes by default)
|
|
46
|
+
batplot allfiles --norm
|
|
47
|
+
|
|
48
|
+
# Batch mode: convert 2theta to Q
|
|
49
|
+
batplot --all --wl 1.5406
|
|
47
50
|
```
|
|
48
51
|
|
|
49
52
|
### Electrochemistry
|
|
@@ -83,6 +86,10 @@ batplot --all geom.bpsg --cpc --mass 5.4 # Apply style+geometry to all CPC fi
|
|
|
83
86
|
# Correlate in-situ XRD with electrochemistry
|
|
84
87
|
# (Place both .xye and .mpt files in same directory)
|
|
85
88
|
batplot --operando --interactive
|
|
89
|
+
|
|
90
|
+
# Operando mode without electrochemistry data
|
|
91
|
+
# (Only .xye files, no .mpt file)
|
|
92
|
+
batplot --operando --interactive
|
|
86
93
|
```
|
|
87
94
|
|
|
88
95
|
## Supported File Formats
|
|
@@ -12,7 +12,7 @@ def _print_general_help() -> None:
|
|
|
12
12
|
"What it does:\n"
|
|
13
13
|
" • XY: XRD/PDF/XAS/... curves\n"
|
|
14
14
|
" • EC: GC/CPC/dQdV/CV (from .csv or .mpt)\n"
|
|
15
|
-
" • Operando: contour maps from a folder of XY and .mpt files
|
|
15
|
+
" • Operando: contour maps from a folder of XY and .mpt files\n"
|
|
16
16
|
" • Batch: export SVG plots for all files in a directory\n\n"
|
|
17
17
|
" • Interactive mode: --interactive flag opens a menu for styling, ranges, fonts, export, sessions\n\n"
|
|
18
18
|
"How to run (basics):\n"
|
|
@@ -34,8 +34,7 @@ def _print_general_help() -> None:
|
|
|
34
34
|
" batplot --cv FILE.txt # EC CV (cyclic voltammetry) from .txt\n"
|
|
35
35
|
" batplot --cv --all # Batch: all .mpt/.txt in directory (CV mode)\n\n"
|
|
36
36
|
" [Operando]\n"
|
|
37
|
-
" batplot --operando [FOLDER] # Operando contour
|
|
38
|
-
" batplot --operando --raw # Operando contour with raw intensity (no normalization)\n\n"
|
|
37
|
+
" batplot --operando [FOLDER] # Operando contour (with or without .mpt file)\n\n"
|
|
39
38
|
"Features:\n"
|
|
40
39
|
" • Quick plotting with sensible defaults, no config files needed\n"
|
|
41
40
|
" • Supports many common file formats (see -h xy/ec/op)\n"
|
|
@@ -58,8 +57,9 @@ def _print_xy_help() -> None:
|
|
|
58
57
|
msg = (
|
|
59
58
|
"XY plots (diffraction/PDF/XAS)\n\n"
|
|
60
59
|
"Supported files: .xye .xy .qye .dat .csv .gr .nor .chik .chir .txt (2-col). CIF overlays supported.\n\n"
|
|
61
|
-
"Axis detection: .qye→Q, .gr→r, .nor→energy, .chik→k, .chir→r, else use --xaxis (Q, 2theta, r, k, energy, rft).\n"
|
|
62
|
-
"If mixing 2θ data in Q, give wavelength per-file (file.xye:1.5406) or global --wl.\n
|
|
60
|
+
"Axis detection: .qye→Q, .gr→r, .nor→energy, .chik→k, .chir→r, else use --xaxis (Q, 2theta, r, k, energy, rft, time).\n"
|
|
61
|
+
"If mixing 2θ data in Q, give wavelength per-file (file.xye:1.5406) or global --wl.\n"
|
|
62
|
+
"For electrochemistry CSV/MPT time-voltage plots, use --xaxis time.\n\n"
|
|
63
63
|
"Examples:\n"
|
|
64
64
|
" batplot a.xye:1.5406 b.qye --stack --interactive\n"
|
|
65
65
|
" batplot a.dat b.xy --wl 1.54 --out fig.svg\n"
|
|
@@ -73,18 +73,19 @@ def _print_xy_help() -> None:
|
|
|
73
73
|
" batplot --all # Export all XY files to SVG\n"
|
|
74
74
|
" batplot --all --xaxis 2theta # Batch mode with custom axis type\n"
|
|
75
75
|
" batplot --all --xrange 10 80 # Batch mode with X-axis range\n"
|
|
76
|
-
" batplot --all --wl 1.5406
|
|
76
|
+
" batplot --all --wl 1.5406 # Batch mode with wavelength conversion\n\n"
|
|
77
77
|
"Tips and options:\n"
|
|
78
78
|
"[XY plot]\n"
|
|
79
79
|
" --interactive : open interactive menu for styling, ranges, fonts, export, sessions\n"
|
|
80
80
|
" --delta/-d <float> : spacing between curves, e.g. --delta 0.1\n"
|
|
81
|
-
" --
|
|
81
|
+
" --norm : normalize intensity to 0-1 range. Stack mode (--stack) auto-normalizes\n"
|
|
82
82
|
" --xrange/-r <min> <max> : set x-axis range, e.g. --xrange 0 10\n"
|
|
83
83
|
" --out/-o <filename> : save figure to file, e.g. --out file.svg\n"
|
|
84
|
-
" --xaxis <type> : set x-axis type (Q, 2theta, r, k, energy, rft, or user defined)
|
|
84
|
+
" --xaxis <type> : set x-axis type (Q, 2theta, r, k, energy, rft, time, or user defined)\n"
|
|
85
|
+
" e.g. --xaxis 2theta, or --xaxis time for electrochemistry CSV/MPT time-voltage plots\n"
|
|
85
86
|
" --wl <float> : set wavelength for Q conversion for all files, e.g. --wl 1.5406\n"
|
|
86
87
|
" --fullprof <args> : FullProf overlay options\n"
|
|
87
|
-
" --stack : stack curves vertically\n"
|
|
88
|
+
" --stack : stack curves vertically (auto-enables normalization)\n"
|
|
88
89
|
)
|
|
89
90
|
print(msg)
|
|
90
91
|
|
|
@@ -130,14 +131,15 @@ def _print_op_help() -> None:
|
|
|
130
131
|
"Operando contour plots\n\n"
|
|
131
132
|
"Example usage:\n"
|
|
132
133
|
" batplot --operando --interactive --wl 0.25995 # Interactive mode with Q conversion\n"
|
|
133
|
-
" batplot --operando --raw --interactive # Raw intensity (no normalization)\n"
|
|
134
134
|
" batplot --operando --xaxis 2theta # Using 2theta axis\n\n"
|
|
135
135
|
" • Folder should contain XY files (.xy/.xye/.qye/.dat).\n"
|
|
136
|
-
" •
|
|
136
|
+
" • Intensity scale is auto-adjusted between min/max values.\n"
|
|
137
137
|
" • If no .qye present, provide --xaxis 2theta or set --wl for Q conversion.\n"
|
|
138
|
-
" • If a
|
|
139
|
-
"
|
|
140
|
-
"
|
|
138
|
+
" • If a .mpt file is present, an EC side panel is added for dual-panel mode.\n"
|
|
139
|
+
" • Without a .mpt file, operando-only mode shows the contour plot alone.\n\n"
|
|
140
|
+
"Interactive (--interactive): resize axes/canvas, change colormap, set intensity range (oz),\n"
|
|
141
|
+
"EC y-axis options (time ↔ ions), geometry tweaks, toggle spines/ticks/labels,\n"
|
|
142
|
+
"print/export/import style, save session.\n"
|
|
141
143
|
)
|
|
142
144
|
print(msg)
|
|
143
145
|
|
|
@@ -158,7 +160,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
158
160
|
parser.add_argument("--convert", "-c", nargs="+", help=argparse.SUPPRESS)
|
|
159
161
|
parser.add_argument("--wl", type=float, help=argparse.SUPPRESS)
|
|
160
162
|
parser.add_argument("--fullprof", nargs="+", type=float, help=argparse.SUPPRESS)
|
|
161
|
-
parser.add_argument("--
|
|
163
|
+
parser.add_argument("--norm", action="store_true", help=argparse.SUPPRESS)
|
|
162
164
|
parser.add_argument("--interactive", action="store_true", help=argparse.SUPPRESS)
|
|
163
165
|
parser.add_argument("--savefig", type=str, help=argparse.SUPPRESS)
|
|
164
166
|
parser.add_argument("--stack", action="store_true", help=argparse.SUPPRESS)
|
|
@@ -278,16 +278,16 @@ def batch_process(directory: str, args):
|
|
|
278
278
|
else:
|
|
279
279
|
x_plot = x
|
|
280
280
|
|
|
281
|
-
# Normalize
|
|
282
|
-
if args
|
|
283
|
-
y_plot = y.copy()
|
|
284
|
-
else:
|
|
281
|
+
# Normalize if --norm flag is set
|
|
282
|
+
if getattr(args, 'norm', False):
|
|
285
283
|
if y.size:
|
|
286
284
|
ymin = float(y.min()); ymax = float(y.max())
|
|
287
285
|
span = ymax - ymin
|
|
288
286
|
y_plot = (y - ymin)/span if span > 0 else np.zeros_like(y)
|
|
289
287
|
else:
|
|
290
288
|
y_plot = y
|
|
289
|
+
else:
|
|
290
|
+
y_plot = y.copy()
|
|
291
291
|
|
|
292
292
|
# Plot and save
|
|
293
293
|
fig_b, ax_b = plt.subplots(figsize=(6,4))
|
|
@@ -309,7 +309,7 @@ def batch_process(directory: str, args):
|
|
|
309
309
|
ax_b.set_xlabel("Radial distance (Å)")
|
|
310
310
|
else:
|
|
311
311
|
ax_b.set_xlabel(r"$2\theta\ (\mathrm{deg})$")
|
|
312
|
-
ax_b.set_ylabel("
|
|
312
|
+
ax_b.set_ylabel("Normalized intensity (a.u.)" if getattr(args, 'norm', False) else "Intensity")
|
|
313
313
|
ax_b.set_title(fname)
|
|
314
314
|
fig_b.subplots_adjust(left=0.18, right=0.97, bottom=0.16, top=0.90)
|
|
315
315
|
out_name = os.path.splitext(fname)[0] + ".svg"
|
|
@@ -28,6 +28,8 @@ from .readers import (
|
|
|
28
28
|
read_mpt_file,
|
|
29
29
|
read_ec_csv_file,
|
|
30
30
|
read_ec_csv_dqdv_file,
|
|
31
|
+
read_csv_time_voltage,
|
|
32
|
+
read_mpt_time_voltage,
|
|
31
33
|
)
|
|
32
34
|
from .cif import (
|
|
33
35
|
simulate_cif_pattern_Q,
|
|
@@ -1090,11 +1092,12 @@ def batplot_main() -> int:
|
|
|
1090
1092
|
except Exception:
|
|
1091
1093
|
pass
|
|
1092
1094
|
try:
|
|
1093
|
-
|
|
1095
|
+
# Call interactive menu regardless of EC presence
|
|
1096
|
+
# When ec_ax is None, EC-related commands will be disabled
|
|
1097
|
+
if operando_ec_interactive_menu is not None:
|
|
1094
1098
|
operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax)
|
|
1095
1099
|
else:
|
|
1096
|
-
|
|
1097
|
-
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.")
|
|
1100
|
+
print("Interactive menu not available.")
|
|
1098
1101
|
except Exception as _ie:
|
|
1099
1102
|
print(f"Interactive menu failed: {_ie}")
|
|
1100
1103
|
_plt.show()
|
|
@@ -1236,7 +1239,7 @@ def batplot_main() -> int:
|
|
|
1236
1239
|
except Exception:
|
|
1237
1240
|
pass
|
|
1238
1241
|
try:
|
|
1239
|
-
if operando_ec_interactive_menu is not None
|
|
1242
|
+
if operando_ec_interactive_menu is not None:
|
|
1240
1243
|
operando_ec_interactive_menu(fig2, ax2, im2, cbar2, ec_ax2)
|
|
1241
1244
|
except Exception as _ie:
|
|
1242
1245
|
print(f"Interactive menu failed: {_ie}")
|
|
@@ -1641,8 +1644,8 @@ def batplot_main() -> int:
|
|
|
1641
1644
|
args_subset = sess.get('args_subset', {})
|
|
1642
1645
|
if 'autoscale' in args_subset:
|
|
1643
1646
|
args.autoscale = bool(args_subset['autoscale'])
|
|
1644
|
-
if '
|
|
1645
|
-
args.
|
|
1647
|
+
if 'norm' in args_subset:
|
|
1648
|
+
args.norm = bool(args_subset['norm'])
|
|
1646
1649
|
except Exception:
|
|
1647
1650
|
pass
|
|
1648
1651
|
try:
|
|
@@ -1701,49 +1704,59 @@ def batplot_main() -> int:
|
|
|
1701
1704
|
# ---------------- Determine X-axis type ----------------
|
|
1702
1705
|
def _ext_token(path):
|
|
1703
1706
|
return os.path.splitext(path)[1].lower() # includes leading dot
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1707
|
+
|
|
1708
|
+
# Check for CSV/MPT files with --xaxis time
|
|
1709
|
+
any_csv = any(f.lower().endswith((".csv", ".mpt")) for f in args.files)
|
|
1710
|
+
use_time_mode = any_csv and args.xaxis and args.xaxis.lower() == "time"
|
|
1711
|
+
|
|
1712
|
+
if use_time_mode:
|
|
1713
|
+
# Special mode: plot time (h) vs voltage (V) for electrochemistry CSV/MPT files
|
|
1714
|
+
axis_mode = "time"
|
|
1715
|
+
else:
|
|
1716
|
+
# Regular XRD/PDF/XAS mode - proceed with normal detection
|
|
1717
|
+
any_qye = any(f.lower().endswith(".qye") for f in args.files)
|
|
1718
|
+
any_gr = any(f.lower().endswith(".gr") for f in args.files)
|
|
1719
|
+
any_nor = any(f.lower().endswith(".nor") for f in args.files)
|
|
1720
|
+
any_chik = any("chik" in _ext_token(f) for f in args.files)
|
|
1721
|
+
any_chir = any("chir" in _ext_token(f) for f in args.files)
|
|
1722
|
+
any_txt = any(f.lower().endswith(".txt") for f in args.files)
|
|
1723
|
+
any_cif = any(f.lower().endswith(".cif") for f in args.files)
|
|
1724
|
+
non_cif_count = sum(0 if f.lower().endswith('.cif') else 1 for f in args.files)
|
|
1725
|
+
cif_only = any_cif and non_cif_count == 0
|
|
1726
|
+
any_lambda = any(":" in f for f in args.files) or args.wl is not None
|
|
1727
|
+
|
|
1728
|
+
# Incompatibilities (no mixing of fundamentally different axis domains)
|
|
1729
|
+
if sum(bool(x) for x in (any_gr, any_nor, any_chik, any_chir, (any_qye or any_lambda or any_cif))) > 1:
|
|
1730
|
+
raise ValueError("Cannot mix .gr (r), .nor (energy), .chik (k), .chir (FT-EXAFS R), and Q/2θ/CIF data together. Split runs.")
|
|
1731
|
+
|
|
1732
|
+
# Automatic axis selection based on file extensions
|
|
1733
|
+
if any_qye:
|
|
1734
|
+
axis_mode = "Q"
|
|
1735
|
+
elif any_gr:
|
|
1736
|
+
axis_mode = "r"
|
|
1737
|
+
elif any_nor:
|
|
1738
|
+
axis_mode = "energy"
|
|
1739
|
+
elif any_chik:
|
|
1740
|
+
axis_mode = "k"
|
|
1741
|
+
elif any_chir:
|
|
1742
|
+
axis_mode = "rft"
|
|
1743
|
+
elif any_txt:
|
|
1744
|
+
# .txt is generic, require --xaxis
|
|
1745
|
+
if args.xaxis:
|
|
1746
|
+
axis_mode = args.xaxis
|
|
1747
|
+
else:
|
|
1748
|
+
raise ValueError("Cannot determine X-axis type for .txt files. Please specify --xaxis (Q, 2theta, r, k, energy, rft, or 'user defined').")
|
|
1749
|
+
elif any_lambda or any_cif:
|
|
1750
|
+
if args.xaxis and args.xaxis.lower() in ("2theta","two_theta","tth"):
|
|
1751
|
+
axis_mode = "2theta"
|
|
1752
|
+
else:
|
|
1753
|
+
# If wavelength is provided, user wants to convert to Q
|
|
1754
|
+
# CIF files are in Q space
|
|
1755
|
+
axis_mode = "Q"
|
|
1756
|
+
elif args.xaxis:
|
|
1733
1757
|
axis_mode = args.xaxis
|
|
1734
1758
|
else:
|
|
1735
|
-
raise ValueError("Cannot determine X-axis type
|
|
1736
|
-
elif any_lambda or any_cif:
|
|
1737
|
-
if args.xaxis and args.xaxis.lower() in ("2theta","two_theta","tth"):
|
|
1738
|
-
axis_mode = "2theta"
|
|
1739
|
-
else:
|
|
1740
|
-
# If wavelength is provided, user wants to convert to Q
|
|
1741
|
-
# CIF files are in Q space
|
|
1742
|
-
axis_mode = "Q"
|
|
1743
|
-
elif args.xaxis:
|
|
1744
|
-
axis_mode = args.xaxis
|
|
1745
|
-
else:
|
|
1746
|
-
raise ValueError("Cannot determine X-axis type (need .qye / .gr / .nor / .chik / .chir / .cif / wavelength / --xaxis). For .txt or unknown file types, use --xaxis Q, 2theta, r, k, energy, rft, or 'user defined'.")
|
|
1759
|
+
raise ValueError("Cannot determine X-axis type (need .qye / .gr / .nor / .chik / .chir / .cif / wavelength / --xaxis). For .txt or unknown file types, use --xaxis Q, 2theta, r, k, energy, rft, or 'user defined'.")
|
|
1747
1760
|
|
|
1748
1761
|
use_Q = axis_mode == "Q"
|
|
1749
1762
|
use_2th = axis_mode == "2theta"
|
|
@@ -1751,6 +1764,7 @@ def batplot_main() -> int:
|
|
|
1751
1764
|
use_E = axis_mode == "energy"
|
|
1752
1765
|
use_k = axis_mode == "k" # NEW
|
|
1753
1766
|
use_rft = axis_mode == "rft" # NEW
|
|
1767
|
+
use_time = axis_mode == "time" # NEW: electrochemistry time mode
|
|
1754
1768
|
|
|
1755
1769
|
# Validate: if using 2theta mode with CIF files, wavelength is required
|
|
1756
1770
|
if use_2th and any_cif and not wavelength_file:
|
|
@@ -1816,8 +1830,19 @@ def batplot_main() -> int:
|
|
|
1816
1830
|
if wavelength_file and not use_r and not use_E and file_ext not in (".gr", ".nor", ".cif"):
|
|
1817
1831
|
label += f" (λ={wavelength_file:.5f} Å)"
|
|
1818
1832
|
|
|
1819
|
-
# ---- Read data (
|
|
1820
|
-
if
|
|
1833
|
+
# ---- Read data (time mode for CSV/MPT or regular mode) ----
|
|
1834
|
+
if use_time and file_ext in ('.csv', '.mpt'):
|
|
1835
|
+
# Time mode: read time (h) vs voltage (V) for electrochemistry files
|
|
1836
|
+
try:
|
|
1837
|
+
if file_ext == '.csv':
|
|
1838
|
+
x, y = read_csv_time_voltage(fname)
|
|
1839
|
+
elif file_ext == '.mpt':
|
|
1840
|
+
x, y = read_mpt_time_voltage(fname)
|
|
1841
|
+
e = None
|
|
1842
|
+
except Exception as e_read:
|
|
1843
|
+
print(f"Error reading {fname} in time mode: {e_read}")
|
|
1844
|
+
continue
|
|
1845
|
+
elif is_cif:
|
|
1821
1846
|
try:
|
|
1822
1847
|
# Simulate pattern directly in Q space regardless of current axis_mode
|
|
1823
1848
|
Q_sim, I_sim = simulate_cif_pattern_Q(fname)
|
|
@@ -1889,15 +1914,18 @@ def batplot_main() -> int:
|
|
|
1889
1914
|
args._warned_extensions.add(file_ext)
|
|
1890
1915
|
print(f"Note: Reading '{file_ext}' file as 2-column (x, y) data. Use --xaxis to specify x-axis type if needed.")
|
|
1891
1916
|
|
|
1892
|
-
# ---- X-axis conversion logic updated (no conversion for energy) ----
|
|
1893
|
-
if
|
|
1917
|
+
# ---- X-axis conversion logic updated (no conversion for energy or time) ----
|
|
1918
|
+
if use_time:
|
|
1919
|
+
# Time mode: data already in hours, no conversion needed
|
|
1920
|
+
x_plot = x
|
|
1921
|
+
elif use_Q and file_ext not in (".qye", ".gr", ".nor"):
|
|
1894
1922
|
if wavelength_file:
|
|
1895
1923
|
theta_rad = np.radians(x/2)
|
|
1896
1924
|
x_plot = 4*np.pi*np.sin(theta_rad)/wavelength_file
|
|
1897
1925
|
else:
|
|
1898
1926
|
x_plot = x
|
|
1899
1927
|
else:
|
|
1900
|
-
# r, energy, or already Q: direct
|
|
1928
|
+
# r, energy, k, rft, or already Q: direct
|
|
1901
1929
|
x_plot = x
|
|
1902
1930
|
|
|
1903
1931
|
# ---- Store full (converted) arrays BEFORE cropping ----
|
|
@@ -1920,7 +1948,9 @@ def batplot_main() -> int:
|
|
|
1920
1948
|
x_plot = x_full
|
|
1921
1949
|
|
|
1922
1950
|
# ---- Normalize (display subset) ----
|
|
1923
|
-
|
|
1951
|
+
# Auto-normalize for --stack mode, or explicit --norm flag
|
|
1952
|
+
should_normalize = args.stack or getattr(args, 'norm', False)
|
|
1953
|
+
if should_normalize:
|
|
1924
1954
|
# Min–max normalization to 0..1 within the currently displayed (cropped) segment
|
|
1925
1955
|
if y_plot.size:
|
|
1926
1956
|
y_min = float(y_plot.min())
|
|
@@ -2295,15 +2325,20 @@ def batplot_main() -> int:
|
|
|
2295
2325
|
elif use_rft: x_label = "Radial distance (Å)"
|
|
2296
2326
|
elif use_Q: x_label = r"Q ($\mathrm{\AA}^{-1}$)"
|
|
2297
2327
|
elif use_2th: x_label = r"$2\theta$ (deg)"
|
|
2328
|
+
elif use_time: x_label = "Time (h)"
|
|
2298
2329
|
elif args.xaxis:
|
|
2299
2330
|
x_label = str(args.xaxis)
|
|
2300
2331
|
else:
|
|
2301
2332
|
x_label = "X"
|
|
2302
2333
|
ax.set_xlabel(x_label, fontsize=16)
|
|
2303
|
-
if
|
|
2304
|
-
|
|
2305
|
-
|
|
2334
|
+
# Y-axis label: normalized if --stack or --norm, or voltage for time mode
|
|
2335
|
+
should_normalize = args.stack or getattr(args, 'norm', False)
|
|
2336
|
+
if use_time:
|
|
2337
|
+
ax.set_ylabel("Voltage (V)", fontsize=16)
|
|
2338
|
+
elif should_normalize:
|
|
2306
2339
|
ax.set_ylabel("Normalized intensity (a.u.)", fontsize=16)
|
|
2340
|
+
else:
|
|
2341
|
+
ax.set_ylabel("Intensity", fontsize=16)
|
|
2307
2342
|
|
|
2308
2343
|
# Store originals for axis-title toggle restoration (t menu bn/ln)
|
|
2309
2344
|
try:
|
|
@@ -59,6 +59,7 @@ def _print_menu():
|
|
|
59
59
|
" l: line",
|
|
60
60
|
" m: marker sizes",
|
|
61
61
|
" c: colors",
|
|
62
|
+
" k: spine colors",
|
|
62
63
|
"ry: show/hide efficiency",
|
|
63
64
|
" t: toggle axes",
|
|
64
65
|
" h: legend",
|
|
@@ -1133,6 +1134,57 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
1133
1134
|
if is_multi_file:
|
|
1134
1135
|
_print_file_list(file_data, current_file_idx)
|
|
1135
1136
|
continue
|
|
1137
|
+
elif key == 'k':
|
|
1138
|
+
# Spine colors (w=top, a=left, s=bottom, d=right)
|
|
1139
|
+
try:
|
|
1140
|
+
print("Set spine colors (with matching tick and label colors):")
|
|
1141
|
+
print(" w : top spine | a : left spine")
|
|
1142
|
+
print(" s : bottom spine | d : right spine")
|
|
1143
|
+
print("Example: w:red a:#4561F7 s:blue d:green")
|
|
1144
|
+
line = input("Enter mappings (e.g., w:red a:#4561F7) or q: ").strip()
|
|
1145
|
+
if line and line.lower() != 'q':
|
|
1146
|
+
push_state("color-spine")
|
|
1147
|
+
# Map wasd to spine names
|
|
1148
|
+
key_to_spine = {'w': 'top', 'a': 'left', 's': 'bottom', 'd': 'right'}
|
|
1149
|
+
tokens = line.split()
|
|
1150
|
+
for token in tokens:
|
|
1151
|
+
if ':' not in token:
|
|
1152
|
+
print(f"Skip malformed token: {token}")
|
|
1153
|
+
continue
|
|
1154
|
+
key_part, color = token.split(':', 1)
|
|
1155
|
+
key_part = key_part.lower()
|
|
1156
|
+
if key_part not in key_to_spine:
|
|
1157
|
+
print(f"Unknown key: {key_part} (use w/a/s/d)")
|
|
1158
|
+
continue
|
|
1159
|
+
spine_name = key_to_spine[key_part]
|
|
1160
|
+
# Set for both axes
|
|
1161
|
+
for curr_ax in [ax, ax2]:
|
|
1162
|
+
if curr_ax is None:
|
|
1163
|
+
continue
|
|
1164
|
+
if spine_name not in curr_ax.spines:
|
|
1165
|
+
continue
|
|
1166
|
+
try:
|
|
1167
|
+
# Set spine color
|
|
1168
|
+
curr_ax.spines[spine_name].set_edgecolor(color)
|
|
1169
|
+
# Set tick colors and axis label color for this axis
|
|
1170
|
+
if spine_name in ('top', 'bottom'):
|
|
1171
|
+
curr_ax.tick_params(axis='x', which='both', colors=color)
|
|
1172
|
+
curr_ax.xaxis.label.set_color(color)
|
|
1173
|
+
else: # left or right
|
|
1174
|
+
curr_ax.tick_params(axis='y', which='both', colors=color)
|
|
1175
|
+
curr_ax.yaxis.label.set_color(color)
|
|
1176
|
+
except Exception as e:
|
|
1177
|
+
print(f"Error setting {spine_name} color: {e}")
|
|
1178
|
+
print(f"Set {spine_name} spine to {color}")
|
|
1179
|
+
fig.canvas.draw()
|
|
1180
|
+
else:
|
|
1181
|
+
print("Canceled.")
|
|
1182
|
+
except Exception as e:
|
|
1183
|
+
print(f"Error in spine color menu: {e}")
|
|
1184
|
+
_print_menu()
|
|
1185
|
+
if is_multi_file:
|
|
1186
|
+
_print_file_list(file_data, current_file_idx)
|
|
1187
|
+
continue
|
|
1136
1188
|
elif key == 'e':
|
|
1137
1189
|
try:
|
|
1138
1190
|
fname = input("Export filename (default .svg if no extension, q=cancel): ").strip()
|