batplot 1.7.20__py3-none-any.whl → 1.7.22__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/args.py +69 -68
- batplot/batplot.py +2 -3
- batplot/cpc_interactive.py +71 -8
- batplot/electrochem_interactive.py +181 -23
- batplot/operando.py +3 -15
- batplot/operando_ec_interactive.py +47 -41
- {batplot-1.7.20.dist-info → batplot-1.7.22.dist-info}/METADATA +1 -1
- {batplot-1.7.20.dist-info → batplot-1.7.22.dist-info}/RECORD +13 -13
- {batplot-1.7.20.dist-info → batplot-1.7.22.dist-info}/WHEEL +0 -0
- {batplot-1.7.20.dist-info → batplot-1.7.22.dist-info}/entry_points.txt +0 -0
- {batplot-1.7.20.dist-info → batplot-1.7.22.dist-info}/licenses/LICENSE +0 -0
- {batplot-1.7.20.dist-info → batplot-1.7.22.dist-info}/top_level.txt +0 -0
batplot/__init__.py
CHANGED
batplot/args.py
CHANGED
|
@@ -9,7 +9,7 @@ the command-line interface, including:
|
|
|
9
9
|
|
|
10
10
|
HOW COMMAND-LINE ARGUMENTS WORK:
|
|
11
11
|
--------------------------------
|
|
12
|
-
When you run 'batplot file.xy --
|
|
12
|
+
When you run (for example) 'batplot --xaxis 2theta file.xy --i', Python's argparse library:
|
|
13
13
|
1. Parses the command line into structured arguments
|
|
14
14
|
2. Validates that required arguments are present
|
|
15
15
|
3. Converts string arguments to appropriate types (int, float, bool, etc.)
|
|
@@ -26,7 +26,7 @@ import sys
|
|
|
26
26
|
import re
|
|
27
27
|
|
|
28
28
|
# ====================================================================
|
|
29
|
-
# COLORED HELP OUTPUT
|
|
29
|
+
# COLORED HELP OUTPUT
|
|
30
30
|
# ====================================================================
|
|
31
31
|
# The 'rich' library provides colored terminal output. If available,
|
|
32
32
|
# we use it to make help text more readable by highlighting:
|
|
@@ -64,8 +64,8 @@ def _colorize_help(text: str) -> str:
|
|
|
64
64
|
- Bullet points: • → bold
|
|
65
65
|
|
|
66
66
|
Example:
|
|
67
|
-
Input: "batplot file.
|
|
68
|
-
Output: "[green]batplot[/green] [yellow]file.
|
|
67
|
+
Input: "batplot file.qye --i"
|
|
68
|
+
Output: "[green]batplot[/green] [yellow]file.qye[/yellow] [cyan]--i[/cyan]"
|
|
69
69
|
|
|
70
70
|
Args:
|
|
71
71
|
text: Plain help text (uncolored)
|
|
@@ -133,78 +133,79 @@ def _print_general_help() -> None:
|
|
|
133
133
|
msg = (
|
|
134
134
|
version_str +
|
|
135
135
|
"What it does:\n"
|
|
136
|
-
" • XY: XRD/PDF/XAS
|
|
137
|
-
" • EC: GC/CPC/dQdV/CV
|
|
138
|
-
" • Operando: contour maps from a folder of
|
|
139
|
-
" • Batch: export
|
|
140
|
-
" • Interactive mode: --i / --interactive flag opens a menu for styling, ranges,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
136
|
+
" • XY: XRD/PDF/XAS/User defined curves\n"
|
|
137
|
+
" • EC: Galvanostatic cycling(GC)/Capacity per cycle(CPC)/Diffrential capacity(dQdV)/Cyclic Voltammetry(CV) from Neware (.csv) or Biologic (.mpt)\n"
|
|
138
|
+
" • Operando: contour maps from a folder of .xy/.xye/.dat/.txt and optional file as side panel\n"
|
|
139
|
+
" • Batch: export vector plots for all files in a directory\n\n"
|
|
140
|
+
" • Interactive mode: --i / --interactive flag opens a menu for styling, ranges, export, and save\n\n"
|
|
141
|
+
"How to run (basics):\n"
|
|
142
|
+
" [1D(XY) curves]\n"
|
|
143
|
+
" batplot file1.xy file2.qye [option1] [option2] # 1D curves, read the first two columns as X and Y axis by default\n"
|
|
144
|
+
" batplot allfiles # Plot all files in current directory on same figure\n"
|
|
145
|
+
" batplot allfiles /path/to/dir # Plot all files in specified directory\n"
|
|
146
|
+
" batplot allfiles --i # Plot all files with interactive menu\n"
|
|
147
|
+
" batplot allxyfiles # Plot only .xy files (natural sorted)\n"
|
|
148
|
+
" batplot /path/to/data allnorfiles --i # Plot only .nor files from a directory\n"
|
|
149
|
+
" batplot --all # Batch mode: all XY files → Figures/ as .svg\n"
|
|
150
|
+
" batplot --all --format png # Batch mode: export as .png files\n"
|
|
151
|
+
" batplot --all --xaxis 2theta --xrange 10 80 # Batch mode with custom axis and range\n"
|
|
152
|
+
" batplot --all style.bps # Batch with style: apply style.bps to all files\n"
|
|
153
|
+
" batplot --all ./Style/style.bps # Batch with style: use relative path to style file\n"
|
|
154
|
+
" batplot --all config.bpsg # Batch with style+geom: apply to all XY files\n"
|
|
155
|
+
" batplot file1.qye file2.qye style.bps # Apply style to multiple files and export\n"
|
|
156
|
+
" batplot file1.xy file2.xye ./Style/style.bps # Apply style from relative path\n\n"
|
|
157
|
+
" [Electrochemistry]\n"
|
|
158
|
+
" batplot --gc file.mpt --mass 7.0 # EC GC from .mpt (requires --mass mg)\n"
|
|
159
|
+
" batplot --gc file.csv --i # EC GC from supported .csv (no mass required) with interactive menu\n"
|
|
160
|
+
" batplot --gc --all --mass 7.0 # Batch: all .mpt/.csv → Figures/ as .svg\n"
|
|
161
|
+
" batplot --gc --all --mass 7 --format png # Batch: export as .png files\n"
|
|
162
|
+
" batplot --all --dqdv style.bps --mass 7 # Batch with style: apply style.bps to all GC files\n"
|
|
163
|
+
" batplot --all --gc ./Style/style.bps --mass 7 # Batch with style: use relative path\n"
|
|
164
|
+
" batplot --all --cpc config.bpsg # Batch with style+geom: apply to all CV files\n"
|
|
165
|
+
" batplot --all --cv ./Style/config.bpsg # Batch with style+geom: use relative path\n"
|
|
166
|
+
" batplot --dqdv FILE.csv # EC dQ/dV from supported .csv\n"
|
|
167
|
+
" batplot --dqdv --all # Batch: all .csv in directory (dQdV mode)\n"
|
|
168
|
+
" batplot --cv FILE.mpt # EC CV (cyclic voltammetry) from .mpt\n"
|
|
169
|
+
" batplot --cv FILE.txt # EC CV (cyclic voltammetry) from .txt\n"
|
|
170
|
+
" batplot --cv --all # Batch: all .mpt/.txt in directory (CV mode)\n\n"
|
|
171
|
+
" [Operando]\n"
|
|
172
|
+
" batplot --operando --i [FOLDER] # Operando contour (with or without .mpt file) with interactive menu\n\n"
|
|
173
|
+
"Features:\n"
|
|
174
|
+
" • Quick plotting with sensible defaults, no config files needed\n"
|
|
175
|
+
" • Supports many common file formats (see -h xy/ec/op)\n"
|
|
176
|
+
" • Interactive menus (--interactive): styling, ranges, fonts, export, sessions\n"
|
|
177
|
+
" • Batch processing: use 'allfiles' / 'all<ext>files' to plot together, or --all for separate files\n"
|
|
178
|
+
" • Batch exports saved to Figures/ subdirectory (default: .svg format)\n"
|
|
179
|
+
" • Batch styling: apply .bps/.bpsg files to all exports (use --all flag)\n"
|
|
180
|
+
" • Format option: use --format png/pdf/jpg/etc to change export format\n\n"
|
|
181
|
+
|
|
182
|
+
"More help:\n"
|
|
183
|
+
" batplot -h xy # XY file plotting guide\n"
|
|
184
|
+
" batplot -h ec # Electrochemistry (GC/dQdV/CV/CPC) guide\n"
|
|
185
|
+
" batplot -h op # Operando guide\n"
|
|
186
|
+
" batplot -m # Open the illustrated txt manual with highlights\n"
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
188
|
+
"Contact & Updates:\n\n"
|
|
189
|
+
" Subscribe to batplot-lab@kjemi.uio.no for updates\n"
|
|
190
|
+
" (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"
|
|
191
|
+
" Kindly cite the pypi package page (https://pypi.org/project/batplot/) if the plot is used for publication\n"
|
|
192
|
+
" Email: tianda@uio.no\n"
|
|
193
|
+
)
|
|
194
194
|
_print_help(msg)
|
|
195
195
|
|
|
196
196
|
|
|
197
197
|
def _print_xy_help() -> None:
|
|
198
198
|
msg = (
|
|
199
|
-
"XY plots (
|
|
200
|
-
"Supported files: .xye .xy .qye .dat .csv .gr .nor .chik .chir .txt
|
|
201
|
-
"Axis detection: .qye→Q, .gr→r, .nor→energy, .chik→k, .chir→r, else use --xaxis (Q, 2theta, r, k, energy,
|
|
202
|
-
"If mixing 2θ data in Q, give wavelength per-file (file.xye:1.5406) or global --wl.\n"
|
|
199
|
+
"XY plots (XRD/PDF/XAS and many more)\n\n"
|
|
200
|
+
"Supported files: .xye .xy .qye .dat .csv .gr .nor .chik .chir .txt and other user specified formats. CIF overlays supported.\n\n"
|
|
201
|
+
"Axis detection: .qye→Q, .gr→r, .nor→energy, .chik→k, .chir→r, else use --xaxis (Q, 2theta, r, k, energy, time or user defined).\n"
|
|
202
|
+
"If mixing 2θ data in Q, give wavelength per-file (file.xye:1.5406) or global flag --wl.\n"
|
|
203
|
+
"A wavelength can be converted into a different wave length by file.xye:1.54:0.709.\n"
|
|
203
204
|
"For electrochemistry CSV/MPT time-voltage plots, use --xaxis time.\n\n"
|
|
204
205
|
"Examples:\n"
|
|
205
|
-
" batplot a.xye:1.5406 b.qye --stack --
|
|
206
|
-
" batplot a.dat b.xy --wl 1.54 --
|
|
207
|
-
" batplot pattern.qye ticks.cif --
|
|
206
|
+
" batplot a.xye:1.5406 b.qye --stack --i\n"
|
|
207
|
+
" batplot a.dat b.xy --wl 1.54 --i\n"
|
|
208
|
+
" batplot pattern.qye ticks.cif:1.54 --i\n\n"
|
|
208
209
|
"Plot all files together:\n"
|
|
209
210
|
" batplot allfiles # Plot all XY files on same figure\n"
|
|
210
211
|
" batplot allfiles /path/to/dir # Plot all XY files in specified directory\n"
|
batplot/batplot.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
"""batplot - Interactive plotting for
|
|
2
|
-
|
|
3
|
-
This module can be imported as a library (safe, no side effects) or run as CLI (via batplot_main()).
|
|
1
|
+
"""batplot - Interactive plotting for 1D, electrochemistry and operando contour plots.
|
|
2
|
+
It is designed for researchers working on materials science and electrochemistry, aiming to speed up the plotting process.
|
|
4
3
|
"""
|
|
5
4
|
|
|
6
5
|
from __future__ import annotations
|
batplot/cpc_interactive.py
CHANGED
|
@@ -428,6 +428,8 @@ def _style_snapshot(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_data=Non
|
|
|
428
428
|
'visible': ax2.spines.get('right').get_visible() if ax2.spines.get('right') else None,
|
|
429
429
|
'color': ax2.spines.get('right').get_edgecolor() if ax2.spines.get('right') else None},
|
|
430
430
|
},
|
|
431
|
+
'spine_colors_auto': getattr(fig, '_cpc_spine_auto', False),
|
|
432
|
+
'spine_colors': dict(getattr(fig, '_cpc_spine_colors', {})),
|
|
431
433
|
'labelpads': {
|
|
432
434
|
'x': getattr(ax.xaxis, 'labelpad', None),
|
|
433
435
|
'ly': getattr(ax.yaxis, 'labelpad', None), # left y-axis (capacity)
|
|
@@ -806,6 +808,24 @@ def _apply_style(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, cfg: Dict, file_
|
|
|
806
808
|
pass
|
|
807
809
|
if spec.get('color') is not None:
|
|
808
810
|
_set_spine_color('right', spec['color'])
|
|
811
|
+
# Restore spine colors from stored dict
|
|
812
|
+
spine_colors = cfg.get('spine_colors', {})
|
|
813
|
+
if spine_colors:
|
|
814
|
+
for spine_name, color in spine_colors.items():
|
|
815
|
+
_set_spine_color(spine_name, color)
|
|
816
|
+
# Restore auto setting
|
|
817
|
+
spine_auto = cfg.get('spine_colors_auto', False)
|
|
818
|
+
if spine_auto is not None:
|
|
819
|
+
fig._cpc_spine_auto = bool(spine_auto)
|
|
820
|
+
# If auto is enabled, apply colors immediately
|
|
821
|
+
if fig._cpc_spine_auto and not (file_data and len(file_data) > 1):
|
|
822
|
+
try:
|
|
823
|
+
charge_col = _color_of(sc_charge)
|
|
824
|
+
eff_col = _color_of(sc_eff)
|
|
825
|
+
_set_spine_color('left', charge_col)
|
|
826
|
+
_set_spine_color('right', eff_col)
|
|
827
|
+
except Exception:
|
|
828
|
+
pass
|
|
809
829
|
except Exception:
|
|
810
830
|
pass
|
|
811
831
|
# Restore labelpads (preserve current if not in config)
|
|
@@ -1428,6 +1448,9 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
1428
1448
|
try:
|
|
1429
1449
|
sc_charge.set_color(charge_col)
|
|
1430
1450
|
sc_discharge.set_color(discharge_col)
|
|
1451
|
+
# Apply auto colors if enabled
|
|
1452
|
+
if not is_multi_file and getattr(fig, '_cpc_spine_auto', False):
|
|
1453
|
+
_set_spine_color('left', charge_col)
|
|
1431
1454
|
except Exception:
|
|
1432
1455
|
pass
|
|
1433
1456
|
try:
|
|
@@ -1555,6 +1578,9 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
1555
1578
|
col = val
|
|
1556
1579
|
try:
|
|
1557
1580
|
sc_eff.set_color(col)
|
|
1581
|
+
# Apply auto colors if enabled
|
|
1582
|
+
if not is_multi_file and getattr(fig, '_cpc_spine_auto', False):
|
|
1583
|
+
_set_spine_color('right', col)
|
|
1558
1584
|
except Exception:
|
|
1559
1585
|
pass
|
|
1560
1586
|
try:
|
|
@@ -1573,12 +1599,42 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
1573
1599
|
elif key == 'k':
|
|
1574
1600
|
# Spine colors (w=top, a=left, s=bottom, d=right)
|
|
1575
1601
|
try:
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1602
|
+
while True:
|
|
1603
|
+
print("\nSet spine colors (with matching tick and label colors):")
|
|
1604
|
+
print(_colorize_inline_commands(" w : top spine | a : left spine"))
|
|
1605
|
+
print(_colorize_inline_commands(" s : bottom spine | d : right spine"))
|
|
1606
|
+
print(_colorize_inline_commands("Example: w:red a:#4561F7 s:blue d:green"))
|
|
1607
|
+
# Add auto function when only one file is loaded
|
|
1608
|
+
if not is_multi_file:
|
|
1609
|
+
auto_enabled = getattr(fig, '_cpc_spine_auto', False)
|
|
1610
|
+
auto_status = "ON" if auto_enabled else "OFF"
|
|
1611
|
+
print(_colorize_inline_commands(f" a : auto (apply capacity curve color to left y-axis, efficiency to right y-axis) [{auto_status}]"))
|
|
1612
|
+
print("q: back to main menu")
|
|
1613
|
+
line = input("Enter mappings (e.g., w:red a:#4561F7) or q: ").strip()
|
|
1614
|
+
if not line or line.lower() == 'q':
|
|
1615
|
+
break
|
|
1616
|
+
# Handle auto toggle when only one file is loaded
|
|
1617
|
+
if not is_multi_file and line.lower() == 'a':
|
|
1618
|
+
auto_enabled = getattr(fig, '_cpc_spine_auto', False)
|
|
1619
|
+
fig._cpc_spine_auto = not auto_enabled
|
|
1620
|
+
new_status = "ON" if fig._cpc_spine_auto else "OFF"
|
|
1621
|
+
print(f"Auto mode: {new_status}")
|
|
1622
|
+
if fig._cpc_spine_auto:
|
|
1623
|
+
# Apply auto colors immediately
|
|
1624
|
+
push_state("color-spine-auto")
|
|
1625
|
+
try:
|
|
1626
|
+
# Get capacity curve color (charge color)
|
|
1627
|
+
charge_col = _color_of(sc_charge)
|
|
1628
|
+
# Get efficiency curve color
|
|
1629
|
+
eff_col = _color_of(sc_eff)
|
|
1630
|
+
# Apply to left and right spines
|
|
1631
|
+
_set_spine_color('left', charge_col)
|
|
1632
|
+
_set_spine_color('right', eff_col)
|
|
1633
|
+
print(f"Applied: left y-axis = {charge_col}, right y-axis = {eff_col}")
|
|
1634
|
+
fig.canvas.draw()
|
|
1635
|
+
except Exception as e:
|
|
1636
|
+
print(f"Error applying auto colors: {e}")
|
|
1637
|
+
continue
|
|
1582
1638
|
push_state("color-spine")
|
|
1583
1639
|
# Map wasd to spine names
|
|
1584
1640
|
key_to_spine = {'w': 'top', 'a': 'left', 's': 'bottom', 'd': 'right'}
|
|
@@ -1597,8 +1653,6 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
1597
1653
|
_set_spine_color(spine_name, resolved)
|
|
1598
1654
|
print(f"Set {spine_name} spine to {resolved}")
|
|
1599
1655
|
fig.canvas.draw()
|
|
1600
|
-
else:
|
|
1601
|
-
print("Canceled.")
|
|
1602
1656
|
except Exception as e:
|
|
1603
1657
|
print(f"Error in spine color menu: {e}")
|
|
1604
1658
|
_print_menu()
|
|
@@ -1775,6 +1829,15 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
1775
1829
|
vis = props.get('visible', False)
|
|
1776
1830
|
col = props.get('color')
|
|
1777
1831
|
print(f" {name:<6} lw={lw} visible={vis} color={col}")
|
|
1832
|
+
# Spine colors (k command)
|
|
1833
|
+
spine_colors = snap.get('spine_colors', {})
|
|
1834
|
+
if spine_colors:
|
|
1835
|
+
print("Spine colors:")
|
|
1836
|
+
for name, color in spine_colors.items():
|
|
1837
|
+
print(f" {name}: {color}")
|
|
1838
|
+
spine_auto = snap.get('spine_colors_auto', False)
|
|
1839
|
+
if spine_auto:
|
|
1840
|
+
print(f"Spine colors auto: ON (capacity → left y-axis, efficiency → right y-axis)")
|
|
1778
1841
|
|
|
1779
1842
|
ticks = snap.get('ticks', {})
|
|
1780
1843
|
print(f"Tick widths: x_major={ticks.get('x_major_width')}, x_minor={ticks.get('x_minor_width')}")
|
|
@@ -227,6 +227,120 @@ def _savgol_smooth(y: np.ndarray, window: int = 9, poly: int = 3) -> np.ndarray:
|
|
|
227
227
|
return smoothed
|
|
228
228
|
|
|
229
229
|
|
|
230
|
+
def _apply_stored_smooth_settings(cycle_lines: Dict[int, Dict[str, Optional[object]]], fig) -> None:
|
|
231
|
+
"""Apply stored smooth settings to newly visible cycles that haven't been smoothed yet."""
|
|
232
|
+
if not hasattr(fig, '_dqdv_smooth_settings'):
|
|
233
|
+
return
|
|
234
|
+
settings = fig._dqdv_smooth_settings
|
|
235
|
+
if not settings:
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
method = settings.get('method')
|
|
239
|
+
if method == 'diffcap':
|
|
240
|
+
min_step = settings.get('min_step', 0.001)
|
|
241
|
+
window = settings.get('window', 9)
|
|
242
|
+
poly = settings.get('poly', 3)
|
|
243
|
+
for cyc, parts in cycle_lines.items():
|
|
244
|
+
iter_parts = [(None, parts)] if not isinstance(parts, dict) else parts.items()
|
|
245
|
+
for role, ln in iter_parts:
|
|
246
|
+
if ln is None or not ln.get_visible():
|
|
247
|
+
continue
|
|
248
|
+
# Only apply if this cycle hasn't been smoothed yet
|
|
249
|
+
if hasattr(ln, '_smooth_applied') and ln._smooth_applied:
|
|
250
|
+
continue
|
|
251
|
+
xdata = np.asarray(ln.get_xdata(), float)
|
|
252
|
+
ydata = np.asarray(ln.get_ydata(), float)
|
|
253
|
+
if xdata.size < 3:
|
|
254
|
+
continue
|
|
255
|
+
# Get original data if available, otherwise use current data
|
|
256
|
+
if hasattr(ln, '_original_xdata'):
|
|
257
|
+
xdata = np.asarray(ln._original_xdata, float)
|
|
258
|
+
ydata = np.asarray(ln._original_ydata, float)
|
|
259
|
+
else:
|
|
260
|
+
ln._original_xdata = np.array(xdata, copy=True)
|
|
261
|
+
ln._original_ydata = np.array(ydata, copy=True)
|
|
262
|
+
x_clean, y_clean, removed = _diffcap_clean_series(xdata, ydata, min_step)
|
|
263
|
+
if x_clean.size < poly + 2:
|
|
264
|
+
continue
|
|
265
|
+
y_smooth = _savgol_smooth(y_clean, window, poly)
|
|
266
|
+
ln.set_xdata(x_clean)
|
|
267
|
+
ln.set_ydata(y_smooth)
|
|
268
|
+
ln._smooth_applied = True
|
|
269
|
+
elif method == 'voltage_step':
|
|
270
|
+
threshold_v = settings.get('threshold_v', 0.0005)
|
|
271
|
+
for cyc, parts in cycle_lines.items():
|
|
272
|
+
for role in ("charge", "discharge"):
|
|
273
|
+
ln = parts.get(role) if isinstance(parts, dict) else parts
|
|
274
|
+
if ln is None or not ln.get_visible():
|
|
275
|
+
continue
|
|
276
|
+
# Only apply if this cycle hasn't been smoothed yet
|
|
277
|
+
if hasattr(ln, '_smooth_applied') and ln._smooth_applied:
|
|
278
|
+
continue
|
|
279
|
+
xdata = np.asarray(ln.get_xdata(), float)
|
|
280
|
+
ydata = np.asarray(ln.get_ydata(), float)
|
|
281
|
+
if xdata.size < 3:
|
|
282
|
+
continue
|
|
283
|
+
# Get original data if available, otherwise use current data
|
|
284
|
+
if hasattr(ln, '_original_xdata'):
|
|
285
|
+
xdata = np.asarray(ln._original_xdata, float)
|
|
286
|
+
ydata = np.asarray(ln._original_ydata, float)
|
|
287
|
+
else:
|
|
288
|
+
ln._original_xdata = np.array(xdata, copy=True)
|
|
289
|
+
ln._original_ydata = np.array(ydata, copy=True)
|
|
290
|
+
dv = np.abs(np.diff(xdata))
|
|
291
|
+
mask = np.ones_like(xdata, dtype=bool)
|
|
292
|
+
mask[1:] &= dv >= threshold_v
|
|
293
|
+
mask[:-1] &= dv >= threshold_v
|
|
294
|
+
filtered_x = xdata[mask]
|
|
295
|
+
filtered_y = ydata[mask]
|
|
296
|
+
if len(filtered_x) < len(xdata):
|
|
297
|
+
ln.set_xdata(filtered_x)
|
|
298
|
+
ln.set_ydata(filtered_y)
|
|
299
|
+
ln._smooth_applied = True
|
|
300
|
+
elif method == 'outlier':
|
|
301
|
+
outlier_method = settings.get('outlier_method', '1')
|
|
302
|
+
threshold = settings.get('threshold', 5.0)
|
|
303
|
+
for cyc, parts in cycle_lines.items():
|
|
304
|
+
for role in ("charge", "discharge"):
|
|
305
|
+
ln = parts.get(role) if isinstance(parts, dict) else parts
|
|
306
|
+
if ln is None or not ln.get_visible():
|
|
307
|
+
continue
|
|
308
|
+
# Only apply if this cycle hasn't been smoothed yet
|
|
309
|
+
if hasattr(ln, '_smooth_applied') and ln._smooth_applied:
|
|
310
|
+
continue
|
|
311
|
+
xdata = np.asarray(ln.get_xdata(), float)
|
|
312
|
+
ydata = np.asarray(ln.get_ydata(), float)
|
|
313
|
+
if xdata.size < 5:
|
|
314
|
+
continue
|
|
315
|
+
# Get original data if available, otherwise use current data
|
|
316
|
+
if hasattr(ln, '_original_xdata'):
|
|
317
|
+
xdata = np.asarray(ln._original_xdata, float)
|
|
318
|
+
ydata = np.asarray(ln._original_ydata, float)
|
|
319
|
+
else:
|
|
320
|
+
ln._original_xdata = np.array(xdata, copy=True)
|
|
321
|
+
ln._original_ydata = np.array(ydata, copy=True)
|
|
322
|
+
if outlier_method == '1':
|
|
323
|
+
mean_y = np.nanmean(ydata)
|
|
324
|
+
std_y = np.nanstd(ydata)
|
|
325
|
+
if not np.isfinite(std_y) or std_y == 0:
|
|
326
|
+
continue
|
|
327
|
+
zscores = np.abs((ydata - mean_y) / std_y)
|
|
328
|
+
mask = zscores <= threshold
|
|
329
|
+
else:
|
|
330
|
+
median_y = np.nanmedian(ydata)
|
|
331
|
+
mad = np.nanmedian(np.abs(ydata - median_y))
|
|
332
|
+
if not np.isfinite(mad) or mad == 0:
|
|
333
|
+
continue
|
|
334
|
+
deviations = np.abs(ydata - median_y) / mad
|
|
335
|
+
mask = deviations <= threshold
|
|
336
|
+
filtered_x = xdata[mask]
|
|
337
|
+
filtered_y = ydata[mask]
|
|
338
|
+
if len(filtered_x) < len(xdata):
|
|
339
|
+
ln.set_xdata(filtered_x)
|
|
340
|
+
ln.set_ydata(filtered_y)
|
|
341
|
+
ln._smooth_applied = True
|
|
342
|
+
|
|
343
|
+
|
|
230
344
|
def _print_menu(n_cycles: int, is_dqdv: bool = False):
|
|
231
345
|
# Three-column menu similar to operando: Styles | Geometries | Options
|
|
232
346
|
# Use dynamic column widths for clean alignment.
|
|
@@ -1202,6 +1316,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1202
1316
|
try:
|
|
1203
1317
|
ax.set_xlim(*snap.get('xlim', ax.get_xlim()))
|
|
1204
1318
|
ax.set_ylim(*snap.get('ylim', ax.get_ylim()))
|
|
1319
|
+
_apply_nice_ticks()
|
|
1205
1320
|
except Exception:
|
|
1206
1321
|
pass
|
|
1207
1322
|
try:
|
|
@@ -1287,6 +1402,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1287
1402
|
if font_size is not None:
|
|
1288
1403
|
mpl.rcParams['font.size'] = font_size
|
|
1289
1404
|
_apply_font_size(ax, font_size)
|
|
1405
|
+
_rebuild_legend(ax)
|
|
1290
1406
|
except Exception:
|
|
1291
1407
|
pass
|
|
1292
1408
|
try:
|
|
@@ -1304,6 +1420,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1304
1420
|
_apply_font_family(ax, font_sans_serif[0])
|
|
1305
1421
|
elif font_family:
|
|
1306
1422
|
_apply_font_family(ax, font_family)
|
|
1423
|
+
_rebuild_legend(ax)
|
|
1307
1424
|
except Exception:
|
|
1308
1425
|
pass
|
|
1309
1426
|
# Title offsets - all four titles
|
|
@@ -2360,22 +2477,24 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2360
2477
|
elif key == 'k':
|
|
2361
2478
|
# Spine colors (w=top, a=left, s=bottom, d=right)
|
|
2362
2479
|
try:
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2480
|
+
while True:
|
|
2481
|
+
print("\nSet spine colors (with matching tick and label colors):")
|
|
2482
|
+
print(_colorize_inline_commands(" w : top spine | a : left spine"))
|
|
2483
|
+
print(_colorize_inline_commands(" s : bottom spine | d : right spine"))
|
|
2484
|
+
print(_colorize_inline_commands("Example: w:red a:#4561F7 s:blue d:green"))
|
|
2485
|
+
user_colors = get_user_color_list(fig)
|
|
2486
|
+
if user_colors:
|
|
2487
|
+
print("\nSaved colors (enter number or u# to reuse):")
|
|
2488
|
+
for idx, color in enumerate(user_colors, 1):
|
|
2489
|
+
print(f" {idx}: {color_block(color)} {color}")
|
|
2490
|
+
print("Type 'u' to edit saved colors.")
|
|
2491
|
+
print("q: back to main menu")
|
|
2492
|
+
line = input("Enter mappings (e.g., w:red a:#4561F7) or q: ").strip()
|
|
2493
|
+
if not line or line.lower() == 'q':
|
|
2494
|
+
break
|
|
2495
|
+
if line.lower() == 'u':
|
|
2496
|
+
manage_user_colors(fig)
|
|
2497
|
+
continue
|
|
2379
2498
|
push_state("color-spine")
|
|
2380
2499
|
key_to_spine = {'w': 'top', 'a': 'left', 's': 'bottom', 'd': 'right'}
|
|
2381
2500
|
tokens = line.split()
|
|
@@ -2409,8 +2528,6 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2409
2528
|
except Exception as e:
|
|
2410
2529
|
print(f"Error setting {spine_name} color: {e}")
|
|
2411
2530
|
fig.canvas.draw()
|
|
2412
|
-
else:
|
|
2413
|
-
print("Canceled.")
|
|
2414
2531
|
except Exception as e:
|
|
2415
2532
|
print(f"Error in spine color menu: {e}")
|
|
2416
2533
|
_print_menu(len(all_cycles), is_dqdv)
|
|
@@ -2874,6 +2991,10 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
2874
2991
|
# Reapply curve linewidth (in case it was set)
|
|
2875
2992
|
_apply_curve_linewidth(fig, cycle_lines)
|
|
2876
2993
|
|
|
2994
|
+
# Apply stored smooth settings to newly visible cycles (only in dQdV mode)
|
|
2995
|
+
if is_dqdv and hasattr(fig, '_dqdv_smooth_settings'):
|
|
2996
|
+
_apply_stored_smooth_settings(cycle_lines, fig)
|
|
2997
|
+
|
|
2877
2998
|
# Rebuild legend and redraw
|
|
2878
2999
|
_rebuild_legend(ax)
|
|
2879
3000
|
_apply_nice_ticks()
|
|
@@ -3331,9 +3452,15 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3331
3452
|
if hasattr(ln, '_original_xdata'):
|
|
3332
3453
|
ln.set_xdata(ln._original_xdata)
|
|
3333
3454
|
ln.set_ydata(ln._original_ydata)
|
|
3455
|
+
# Clear smooth flag so smooth can be reapplied if needed
|
|
3456
|
+
if hasattr(ln, '_smooth_applied'):
|
|
3457
|
+
delattr(ln, '_smooth_applied')
|
|
3334
3458
|
restored_count += 1
|
|
3335
3459
|
if restored_count:
|
|
3336
3460
|
print(f"Reset {restored_count} curve(s) to original data.")
|
|
3461
|
+
# Clear stored smooth settings
|
|
3462
|
+
if hasattr(fig, '_dqdv_smooth_settings'):
|
|
3463
|
+
fig._dqdv_smooth_settings = {}
|
|
3337
3464
|
fig.canvas.draw_idle()
|
|
3338
3465
|
else:
|
|
3339
3466
|
print("No filtered data to reset.")
|
|
@@ -3365,6 +3492,13 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3365
3492
|
print("Threshold must be positive.")
|
|
3366
3493
|
continue
|
|
3367
3494
|
push_state("smooth-apply")
|
|
3495
|
+
# Store smooth settings for future cycle changes
|
|
3496
|
+
if not hasattr(fig, '_dqdv_smooth_settings'):
|
|
3497
|
+
fig._dqdv_smooth_settings = {}
|
|
3498
|
+
fig._dqdv_smooth_settings.update({
|
|
3499
|
+
'method': 'voltage_step',
|
|
3500
|
+
'threshold_v': threshold_v
|
|
3501
|
+
})
|
|
3368
3502
|
filtered = 0
|
|
3369
3503
|
total_before = 0
|
|
3370
3504
|
total_after = 0
|
|
@@ -3391,6 +3525,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3391
3525
|
if after < before:
|
|
3392
3526
|
ln.set_xdata(filtered_x)
|
|
3393
3527
|
ln.set_ydata(filtered_y)
|
|
3528
|
+
ln._smooth_applied = True
|
|
3394
3529
|
filtered += 1
|
|
3395
3530
|
total_before += before
|
|
3396
3531
|
total_after += after
|
|
@@ -3428,7 +3563,8 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3428
3563
|
print("ΔV threshold must be positive.")
|
|
3429
3564
|
continue
|
|
3430
3565
|
break
|
|
3431
|
-
if
|
|
3566
|
+
# Only skip if user explicitly quit with 'q', not if they pressed Enter (empty = use default)
|
|
3567
|
+
if delta_input and delta_input.lower() == 'q': # User quit at previous step
|
|
3432
3568
|
continue
|
|
3433
3569
|
while True:
|
|
3434
3570
|
window_input = input("Savitzky–Golay window (odd, default 9, 'q'=quit, 'e'=explain): ").strip()
|
|
@@ -3446,7 +3582,8 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3446
3582
|
continue
|
|
3447
3583
|
window = 9 if not window_input else int(window_input)
|
|
3448
3584
|
break
|
|
3449
|
-
if
|
|
3585
|
+
# Only skip if user explicitly quit with 'q', not if they pressed Enter (empty = use default)
|
|
3586
|
+
if window_input and window_input.lower() == 'q': # User quit at previous step
|
|
3450
3587
|
continue
|
|
3451
3588
|
while True:
|
|
3452
3589
|
poly_input = input("Polynomial order (default 3, 'q'=quit, 'e'=explain): ").strip()
|
|
@@ -3465,7 +3602,8 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3465
3602
|
continue
|
|
3466
3603
|
poly = 3 if not poly_input else int(poly_input)
|
|
3467
3604
|
break
|
|
3468
|
-
if
|
|
3605
|
+
# Only skip if user explicitly quit with 'q', not if they pressed Enter (empty = use default)
|
|
3606
|
+
if poly_input and poly_input.lower() == 'q': # User quit at previous step
|
|
3469
3607
|
continue
|
|
3470
3608
|
except ValueError:
|
|
3471
3609
|
print("Invalid number.")
|
|
@@ -3477,6 +3615,15 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3477
3615
|
if poly < 1:
|
|
3478
3616
|
poly = 1
|
|
3479
3617
|
push_state("smooth-diffcap")
|
|
3618
|
+
# Store smooth settings for future cycle changes
|
|
3619
|
+
if not hasattr(fig, '_dqdv_smooth_settings'):
|
|
3620
|
+
fig._dqdv_smooth_settings = {}
|
|
3621
|
+
fig._dqdv_smooth_settings.update({
|
|
3622
|
+
'method': 'diffcap',
|
|
3623
|
+
'min_step': min_step,
|
|
3624
|
+
'window': window,
|
|
3625
|
+
'poly': poly
|
|
3626
|
+
})
|
|
3480
3627
|
cleaned_curves = 0
|
|
3481
3628
|
total_removed = 0
|
|
3482
3629
|
for cyc, parts in cycle_lines.items():
|
|
@@ -3497,6 +3644,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3497
3644
|
y_smooth = _savgol_smooth(y_clean, window, poly)
|
|
3498
3645
|
ln.set_xdata(x_clean)
|
|
3499
3646
|
ln.set_ydata(y_smooth)
|
|
3647
|
+
ln._smooth_applied = True
|
|
3500
3648
|
cleaned_curves += 1
|
|
3501
3649
|
total_removed += removed
|
|
3502
3650
|
if cleaned_curves:
|
|
@@ -3570,9 +3718,19 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3570
3718
|
print("Threshold must be positive.")
|
|
3571
3719
|
continue
|
|
3572
3720
|
break
|
|
3573
|
-
if
|
|
3721
|
+
# Only skip if user explicitly quit with 'q', not if they pressed Enter (empty = use default)
|
|
3722
|
+
if thresh_input and thresh_input.lower() == 'q': # User quit
|
|
3574
3723
|
continue
|
|
3575
3724
|
push_state("smooth-outlier")
|
|
3725
|
+
# Store smooth settings for future cycle changes
|
|
3726
|
+
if not hasattr(fig, '_dqdv_smooth_settings'):
|
|
3727
|
+
fig._dqdv_smooth_settings = {}
|
|
3728
|
+
thresh_val = z_threshold if method == '1' else mad_threshold
|
|
3729
|
+
fig._dqdv_smooth_settings.update({
|
|
3730
|
+
'method': 'outlier',
|
|
3731
|
+
'outlier_method': method,
|
|
3732
|
+
'threshold': thresh_val
|
|
3733
|
+
})
|
|
3576
3734
|
filtered = 0
|
|
3577
3735
|
total_before = 0
|
|
3578
3736
|
total_after = 0
|
|
@@ -3609,6 +3767,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3609
3767
|
if after < before:
|
|
3610
3768
|
ln.set_xdata(filtered_x)
|
|
3611
3769
|
ln.set_ydata(filtered_y)
|
|
3770
|
+
ln._smooth_applied = True
|
|
3612
3771
|
filtered += 1
|
|
3613
3772
|
total_before += before
|
|
3614
3773
|
total_after += after
|
|
@@ -3616,7 +3775,6 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
3616
3775
|
removed = total_before - total_after
|
|
3617
3776
|
pct = 100 * removed / total_before if total_before else 0
|
|
3618
3777
|
method_name = "Z-score" if method == '1' else "MAD"
|
|
3619
|
-
thresh_val = z_threshold if method == '1' else mad_threshold
|
|
3620
3778
|
print(f"Removed outliers from {filtered} curve(s) using {method_name} (threshold={thresh_val}).")
|
|
3621
3779
|
print(f"Removed {removed} of {total_before} points ({pct:.1f}%).")
|
|
3622
3780
|
print("Tip: Adjust threshold to control sensitivity (always applied to raw data).")
|
batplot/operando.py
CHANGED
|
@@ -11,11 +11,6 @@ An operando plot is a 2D visualization where:
|
|
|
11
11
|
- Y-axis: Time/scan number (which file/measurement in sequence)
|
|
12
12
|
- Color/Z-axis: Intensity (how bright the diffraction signal is)
|
|
13
13
|
|
|
14
|
-
Think of it like a "heat map" where:
|
|
15
|
-
- Each row is one diffraction pattern (one file)
|
|
16
|
-
- Each column is one angle/Q value
|
|
17
|
-
- Color intensity shows how strong the diffraction signal is
|
|
18
|
-
|
|
19
14
|
Example use cases:
|
|
20
15
|
- Watching a material transform during heating (phase transitions)
|
|
21
16
|
- Monitoring battery electrode changes during cycling
|
|
@@ -24,12 +19,12 @@ Example use cases:
|
|
|
24
19
|
|
|
25
20
|
HOW IT WORKS:
|
|
26
21
|
------------
|
|
27
|
-
1. Scan folder for
|
|
22
|
+
1. Scan folder for XRD/PDF/XAS or other data files
|
|
28
23
|
2. Load each file as one "scan" (one row in the contour)
|
|
29
24
|
3. Create a common X-axis grid (interpolate all scans to same grid)
|
|
30
25
|
4. Stack all scans vertically to form a 2D array
|
|
31
26
|
5. Display as intensity contour (color map)
|
|
32
|
-
6. Optionally add electrochemistry data as side panel (if .mpt file present)
|
|
27
|
+
6. Optionally add electrochemistry/temperature/other data as side panel (if .mpt file present)
|
|
33
28
|
|
|
34
29
|
AXIS MODE DETECTION:
|
|
35
30
|
-------------------
|
|
@@ -37,14 +32,7 @@ The X-axis type is determined automatically:
|
|
|
37
32
|
- If --xaxis Q specified → Use Q-space
|
|
38
33
|
- If files are .qye → Use Q-space (already in Q)
|
|
39
34
|
- If --wl specified → Convert 2θ to Q using wavelength
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
NO NORMALIZATION:
|
|
43
|
-
---------------
|
|
44
|
-
Unlike some other modes, operando plots don't normalize intensity.
|
|
45
|
-
The color scale spans from minimum to maximum intensity across ALL scans.
|
|
46
|
-
This preserves the absolute intensity information, which is important for
|
|
47
|
-
comparing different time points.
|
|
35
|
+
- Other operando data (such as PDF/XAS or others) → Plot the first two columns as X and Y
|
|
48
36
|
"""
|
|
49
37
|
from __future__ import annotations
|
|
50
38
|
|
|
@@ -2000,19 +2000,20 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2000
2000
|
ax.set_ylim(y1, y0)
|
|
2001
2001
|
except Exception as e:
|
|
2002
2002
|
print(f"Operando reverse failed: {e}")
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2003
|
+
if ec_ax is not None:
|
|
2004
|
+
try:
|
|
2005
|
+
ey0, ey1 = ec_ax.get_ylim()
|
|
2006
|
+
ec_ax.set_ylim(ey1, ey0)
|
|
2007
|
+
# If we have a stored time ylim for restoration later, invert it too
|
|
2008
|
+
if hasattr(ec_ax, '_saved_time_ylim') and isinstance(ec_ax._saved_time_ylim, (tuple, list)) and len(ec_ax._saved_time_ylim)==2:
|
|
2009
|
+
lo, hi = ec_ax._saved_time_ylim
|
|
2010
|
+
try:
|
|
2011
|
+
ec_ax._saved_time_ylim = (hi, lo)
|
|
2012
|
+
except Exception:
|
|
2013
|
+
pass
|
|
2014
|
+
fig.canvas.draw_idle()
|
|
2015
|
+
except Exception as e:
|
|
2016
|
+
print(f"EC reverse failed: {e}")
|
|
2016
2017
|
print_menu()
|
|
2017
2018
|
elif cmd == 'f':
|
|
2018
2019
|
# Font submenu with numbered options
|
|
@@ -2104,11 +2105,12 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2104
2105
|
ax.tick_params(axis='both', which='major', width=tick_w)
|
|
2105
2106
|
ax.tick_params(axis='both', which='minor', width=tick_w)
|
|
2106
2107
|
|
|
2107
|
-
# Apply to EC pane (ec_ax)
|
|
2108
|
-
|
|
2109
|
-
spine.
|
|
2110
|
-
|
|
2111
|
-
|
|
2108
|
+
# Apply to EC pane (ec_ax) if present
|
|
2109
|
+
if ec_ax is not None:
|
|
2110
|
+
for spine in ec_ax.spines.values():
|
|
2111
|
+
spine.set_linewidth(frame_w)
|
|
2112
|
+
ec_ax.tick_params(axis='both', which='major', width=tick_w)
|
|
2113
|
+
ec_ax.tick_params(axis='both', which='minor', width=tick_w)
|
|
2112
2114
|
|
|
2113
2115
|
# Also apply to colorbar if present
|
|
2114
2116
|
if cbar is not None:
|
|
@@ -2125,7 +2127,10 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2125
2127
|
except Exception:
|
|
2126
2128
|
fig.canvas.draw_idle()
|
|
2127
2129
|
|
|
2128
|
-
|
|
2130
|
+
if ec_ax is not None:
|
|
2131
|
+
print(f"Applied: frame={frame_w:.2f}, ticks={tick_w:.2f} to operando, EC, and colorbar")
|
|
2132
|
+
else:
|
|
2133
|
+
print(f"Applied: frame={frame_w:.2f}, ticks={tick_w:.2f} to operando and colorbar")
|
|
2129
2134
|
except ValueError:
|
|
2130
2135
|
print("Invalid number format.")
|
|
2131
2136
|
except Exception as e:
|
|
@@ -4027,28 +4032,29 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4027
4032
|
except Exception as e:
|
|
4028
4033
|
print(f"Warning: Could not apply operando reverse: {e}")
|
|
4029
4034
|
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4035
|
+
if ec_ax is not None:
|
|
4036
|
+
try:
|
|
4037
|
+
# EC Y-axis reverse
|
|
4038
|
+
ec_cfg = cfg.get('ec', {})
|
|
4039
|
+
ec_y_reversed = ec_cfg.get('y_reversed', False)
|
|
4040
|
+
if ec_y_reversed:
|
|
4041
|
+
ey0, ey1 = ec_ax.get_ylim()
|
|
4042
|
+
if ey0 < ey1: # Only reverse if not already reversed
|
|
4043
|
+
ec_ax.set_ylim(ey1, ey0)
|
|
4044
|
+
# Also update stored time ylim if present
|
|
4045
|
+
if hasattr(ec_ax, '_saved_time_ylim') and isinstance(ec_ax._saved_time_ylim, (tuple, list)) and len(ec_ax._saved_time_ylim)==2:
|
|
4046
|
+
lo, hi = ec_ax._saved_time_ylim
|
|
4047
|
+
ec_ax._saved_time_ylim = (hi, lo)
|
|
4048
|
+
else:
|
|
4049
|
+
ey0, ey1 = ec_ax.get_ylim()
|
|
4050
|
+
if ey0 > ey1: # Un-reverse if currently reversed
|
|
4051
|
+
ec_ax.set_ylim(ey1, ey0)
|
|
4052
|
+
# Also update stored time ylim if present
|
|
4053
|
+
if hasattr(ec_ax, '_saved_time_ylim') and isinstance(ec_ax._saved_time_ylim, (tuple, list)) and len(ec_ax._saved_time_ylim)==2:
|
|
4054
|
+
lo, hi = ec_ax._saved_time_ylim
|
|
4055
|
+
ec_ax._saved_time_ylim = (hi, lo)
|
|
4056
|
+
except Exception as e:
|
|
4057
|
+
print(f"Warning: Could not apply EC reverse: {e}")
|
|
4052
4058
|
|
|
4053
4059
|
# Apply intensity range (oz command)
|
|
4054
4060
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: batplot
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.22
|
|
4
4
|
Summary: Interactive plotting tool for material science (1D plot) and electrochemistry (GC, CV, dQ/dV, CPC, operando) with batch processing
|
|
5
5
|
Author-email: Tian Dai <tianda@uio.no>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
batplot/__init__.py,sha256=
|
|
2
|
-
batplot/args.py,sha256=
|
|
1
|
+
batplot/__init__.py,sha256=49tgsxXciOUfTdUerVfqotJBtQQT93XXCs_pBD-1y2k,119
|
|
2
|
+
batplot/args.py,sha256=T10bmG09tgMks6YvlurHECLvwl94zcvdGZKbc9SuXxQ,34899
|
|
3
3
|
batplot/batch.py,sha256=YQ7obCIqLCObwDbM7TXpOBh7g7BO95wZNsa2Fy84c6o,53858
|
|
4
|
-
batplot/batplot.py,sha256=
|
|
4
|
+
batplot/batplot.py,sha256=h960XKSmJ825DqI99Xnyomg_gaB0aFDUIKxkovm8BPQ,170691
|
|
5
5
|
batplot/cif.py,sha256=JfHwNf3SHrcpALc_F5NjJmQ3lg71MBRSaIUJjGYPTx8,30120
|
|
6
6
|
batplot/cli.py,sha256=ScDb2je8VQ0mz_z0SLCHEigiTuFPY5pb1snnzCouKms,5828
|
|
7
7
|
batplot/color_utils.py,sha256=ow2ElqjIWFLRdrnLwQvrnfa3w3IEB0FodPFdoDQR_Dc,19990
|
|
8
8
|
batplot/config.py,sha256=6nGY7fKN4T5KZUGQS2ArUBgEkLAL0j37XwG5SCVQgKA,6420
|
|
9
9
|
batplot/converters.py,sha256=rR2WMPM0nR5E3eZI3gWbaJf_AfbdQx3urVSbJmZXNzo,8237
|
|
10
|
-
batplot/cpc_interactive.py,sha256=
|
|
11
|
-
batplot/electrochem_interactive.py,sha256=
|
|
10
|
+
batplot/cpc_interactive.py,sha256=ICrY25rSqKsds9Cb2lW1GiTdY-lTQaAuD4NRzQCZGCw,187304
|
|
11
|
+
batplot/electrochem_interactive.py,sha256=f71CsXDNoge-7ENKX3PhmnSf7uBFXoHhcPSHL9_cF-Q,209167
|
|
12
12
|
batplot/interactive.py,sha256=gAmfHLVu4dAC_-hpmpfJj8dyQTBbzRNweVODuQDIocw,196317
|
|
13
13
|
batplot/manual.py,sha256=pbRI6G4Pm12pOW8LrOLWWu7IEOtqWN3tRHtgge50LlA,11556
|
|
14
14
|
batplot/modes.py,sha256=qE2OsOQQKhwOWene5zxJeuuewTrZxubtahQuz5je7ok,37252
|
|
15
|
-
batplot/operando.py,sha256=
|
|
16
|
-
batplot/operando_ec_interactive.py,sha256=
|
|
15
|
+
batplot/operando.py,sha256=CdTZJa6Cr1wNczFEbwAido2mc7C_h1xxoQ5b045ktSk,28105
|
|
16
|
+
batplot/operando_ec_interactive.py,sha256=FpBdtkYdLOa31teRnNZ5qn1fo4Xqrk3AaFwCbj32c3A,280504
|
|
17
17
|
batplot/plotting.py,sha256=hG2_EdDhF1Qpn1XfZKdCQ5-w_m9gUYFbr804UQ5QjsU,10841
|
|
18
18
|
batplot/readers.py,sha256=kAI0AvYrdfGRZkvADJ4riN96IWtrH24aAoZpBtONTbw,112960
|
|
19
19
|
batplot/session.py,sha256=5PVxu8Q7T6Fonq0zgvUD3SkoN-9QnlBa8l3_R02B_K0,120820
|
|
@@ -22,7 +22,7 @@ batplot/ui.py,sha256=MIY2x_ghCYxjdYhjMUZsMMnQEUBLgrIT37hfPGZf_cs,36320
|
|
|
22
22
|
batplot/utils.py,sha256=3dBZALWiCu5c6uc5MBII7n8329BZjieTEw4qithTlow,33939
|
|
23
23
|
batplot/version_check.py,sha256=OG4LuHo5-rSqLLHQo5nWbX9lbNq6NyxRdvVUUcJRBqQ,6219
|
|
24
24
|
batplot/data/USER_MANUAL.md,sha256=VYPvNZt3Fy8Z4Izr2FnQBw9vEaFTPkybhHDnF-OuKws,17694
|
|
25
|
-
batplot-1.7.
|
|
25
|
+
batplot-1.7.22.dist-info/licenses/LICENSE,sha256=2PAnHeCiTfgI7aKZLWr0G56HI9fGKQ0CEbQ02H-yExQ,1065
|
|
26
26
|
batplot_backup_20251121_223043/__init__.py,sha256=3s2DUQuTbWs65hoN9cQQ8IiJbaFJY8fNxiCpwRBYoOA,118
|
|
27
27
|
batplot_backup_20251121_223043/args.py,sha256=OH-h84QhN-IhMS8sPAsSEqccHD3wpeMgmXa_fqv5xtg,21215
|
|
28
28
|
batplot_backup_20251121_223043/batch.py,sha256=oI7PONJyciHDOqNPq-8fnOQMyn9CpAdVznKaEdsy0ig,48650
|
|
@@ -45,8 +45,8 @@ batplot_backup_20251121_223043/style.py,sha256=xg-tj6bEbFUVjjxYMokiLehS4tSfKanLI
|
|
|
45
45
|
batplot_backup_20251121_223043/ui.py,sha256=K0XZWyiuBRNkFod9mgZyJ9CLN78GR1-hh6EznnIb5S8,31208
|
|
46
46
|
batplot_backup_20251121_223043/utils.py,sha256=jydA0JxsCWWAudXEwSjlxTG17y2F8U6hIAukAzi1P0g,32526
|
|
47
47
|
batplot_backup_20251121_223043/version_check.py,sha256=vlHkGkgUJcD_Z4KZmwonxZvKZh0MwHLaBSxaLPc66AQ,4555
|
|
48
|
-
batplot-1.7.
|
|
49
|
-
batplot-1.7.
|
|
50
|
-
batplot-1.7.
|
|
51
|
-
batplot-1.7.
|
|
52
|
-
batplot-1.7.
|
|
48
|
+
batplot-1.7.22.dist-info/METADATA,sha256=huY1GKnOrNjHphobQkIhbe94RtCDgTlwTNjmJRBQLIc,6137
|
|
49
|
+
batplot-1.7.22.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
50
|
+
batplot-1.7.22.dist-info/entry_points.txt,sha256=73GgH3Zs-qGIvgiyQLgGsSW-ryOwPPKHveOW6TDIR5Q,82
|
|
51
|
+
batplot-1.7.22.dist-info/top_level.txt,sha256=CgqK4RpsYnUFAcqO4bLOnEhCoPY4IPEGLPkiDlzLIxg,39
|
|
52
|
+
batplot-1.7.22.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|