batplot 1.8.38__tar.gz → 1.8.39__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.8.38/batplot.egg-info → batplot-1.8.39}/PKG-INFO +1 -1
- {batplot-1.8.38 → batplot-1.8.39}/batplot/__init__.py +1 -1
- {batplot-1.8.38 → batplot-1.8.39}/batplot/args.py +2 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/batplot.py +33 -11
- {batplot-1.8.38 → batplot-1.8.39}/batplot/cpc_interactive.py +44 -35
- {batplot-1.8.38 → batplot-1.8.39}/batplot/data/CHANGELOG.md +4 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/interactive.py +124 -61
- {batplot-1.8.38 → batplot-1.8.39}/batplot/operando.py +62 -55
- {batplot-1.8.38 → batplot-1.8.39}/batplot/style.py +13 -1
- {batplot-1.8.38 → batplot-1.8.39}/batplot/version_check.py +2 -2
- {batplot-1.8.38 → batplot-1.8.39/batplot.egg-info}/PKG-INFO +1 -1
- {batplot-1.8.38 → batplot-1.8.39}/pyproject.toml +1 -1
- {batplot-1.8.38 → batplot-1.8.39}/LICENSE +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/MANIFEST.in +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/NOTICE +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/README.md +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/batch.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/canvas_interactive.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/cif.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/cli.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/color_utils.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/config.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/converters.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/data/USER_MANUAL.md +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/dev_upgrade.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/electrochem_interactive.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/manual.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/modes.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/operando_ec_interactive.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/plotting.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/readers.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/session.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/showcol.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/ui.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot/utils.py +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot.egg-info/SOURCES.txt +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot.egg-info/dependency_links.txt +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot.egg-info/entry_points.txt +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot.egg-info/requires.txt +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/batplot.egg-info/top_level.txt +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/setup.cfg +0 -0
- {batplot-1.8.38 → batplot-1.8.39}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: batplot
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.39
|
|
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: MIT License
|
|
@@ -234,6 +234,8 @@ def _print_xy_help() -> None:
|
|
|
234
234
|
" batplot f1.txt --readcol 2 3 f2.txt --readcol 5 6 --convert 1.54 q\n"
|
|
235
235
|
" Directory: pass a folder to convert all .xy/.xye/.qye/.dat/.csv/.txt files:\n"
|
|
236
236
|
" batplot /path/to/folder --convert 0.25448 1.54\n"
|
|
237
|
+
" Batch in current folder: use allfiles token (non-convertible files are skipped):\n"
|
|
238
|
+
" batplot allfiles --convert q 1.54\n"
|
|
237
239
|
" Examples:\n"
|
|
238
240
|
" batplot file.xye --convert 1.54 0.25\n"
|
|
239
241
|
" batplot file.xye --convert 1.54 q\n"
|
|
@@ -2624,7 +2624,11 @@ def batplot_main() -> int: # type: ignore
|
|
|
2624
2624
|
expanded = []
|
|
2625
2625
|
for p in args.files:
|
|
2626
2626
|
if os.path.isfile(p):
|
|
2627
|
-
|
|
2627
|
+
ext = os.path.splitext(p)[1].lower()
|
|
2628
|
+
if ext in convert_ext:
|
|
2629
|
+
expanded.append(p)
|
|
2630
|
+
else:
|
|
2631
|
+
print(f"Warning: Skipping non-convertible file: {p}")
|
|
2628
2632
|
elif os.path.isdir(p):
|
|
2629
2633
|
for f in sorted(os.listdir(p), key=natural_sort_key):
|
|
2630
2634
|
fp = os.path.join(p, f)
|
|
@@ -3351,6 +3355,10 @@ def batplot_main() -> int: # type: ignore
|
|
|
3351
3355
|
stack_label_bottom = bool(sess.get('stack_label_at_bottom', False))
|
|
3352
3356
|
update_labels(ax, y_data_list, label_text_objects, saved_stack, stack_label_bottom)
|
|
3353
3357
|
if cif_tick_series:
|
|
3358
|
+
try:
|
|
3359
|
+
fig._batplot_cif_tick_series = cif_tick_series
|
|
3360
|
+
except Exception:
|
|
3361
|
+
pass
|
|
3354
3362
|
# Provide draw/extend helpers compatible with interactive menu using original placement logic
|
|
3355
3363
|
def _session_q_to_2theta(peaksQ, wl):
|
|
3356
3364
|
if wl is None:
|
|
@@ -3364,7 +3372,8 @@ def batplot_main() -> int: # type: ignore
|
|
|
3364
3372
|
|
|
3365
3373
|
def _session_ensure_wavelength(default_wl=1.5406):
|
|
3366
3374
|
# Prefer any stored wl, else args.wl, else provided default
|
|
3367
|
-
|
|
3375
|
+
_ser = getattr(fig, '_batplot_cif_tick_series', None) or cif_tick_series
|
|
3376
|
+
for _lab,_fname,_peaks,_wl,_qmax,_color in _ser:
|
|
3368
3377
|
if _wl is not None:
|
|
3369
3378
|
return _wl
|
|
3370
3379
|
return getattr(args, 'wl', None) or default_wl
|
|
@@ -3374,7 +3383,10 @@ def batplot_main() -> int: # type: ignore
|
|
|
3374
3383
|
return
|
|
3375
3384
|
|
|
3376
3385
|
def _session_cif_draw():
|
|
3377
|
-
|
|
3386
|
+
cif_series_draw = getattr(fig, '_batplot_cif_tick_series', None)
|
|
3387
|
+
if cif_series_draw is None:
|
|
3388
|
+
cif_series_draw = cif_tick_series
|
|
3389
|
+
if not cif_series_draw:
|
|
3378
3390
|
return
|
|
3379
3391
|
try:
|
|
3380
3392
|
# Preserve current limits before drawing - use actual current limits
|
|
@@ -3435,7 +3447,7 @@ def batplot_main() -> int: # type: ignore
|
|
|
3435
3447
|
stacked_or_multi_y=_stacked_s,
|
|
3436
3448
|
)
|
|
3437
3449
|
_cif_bottom_m = xy_cif_stack_bottom_margin_yr(fixed_yr, show_titles=show_titles_local)
|
|
3438
|
-
needed_min = base - (len(
|
|
3450
|
+
needed_min = base - (len(cif_series_draw) - 1) * spacing - _cif_bottom_m
|
|
3439
3451
|
if not show_titles_local:
|
|
3440
3452
|
ylim_draw = tuple(prev_ylim)
|
|
3441
3453
|
elif needed_min >= prev_ylim[0]:
|
|
@@ -3457,7 +3469,7 @@ def batplot_main() -> int: # type: ignore
|
|
|
3457
3469
|
wl_any = _session_ensure_wavelength()
|
|
3458
3470
|
|
|
3459
3471
|
# Draw each series
|
|
3460
|
-
for i,(lab,fname,peaksQ,wl,qmax_sim,color) in enumerate(
|
|
3472
|
+
for i,(lab,fname,peaksQ,wl,qmax_sim,color) in enumerate(cif_series_draw):
|
|
3461
3473
|
y_line = base - i * spacing + xy_cif_stack_y_offset(fig, i)
|
|
3462
3474
|
tick_h, hkl_y = xy_cif_tick_stack_layout(y_line, yr)
|
|
3463
3475
|
# Convert peaks to axis domain
|
|
@@ -3621,7 +3633,7 @@ def batplot_main() -> int: # type: ignore
|
|
|
3621
3633
|
cif_globals_dict = None
|
|
3622
3634
|
if cif_tick_series:
|
|
3623
3635
|
cif_globals_dict = {
|
|
3624
|
-
'cif_tick_series':
|
|
3636
|
+
'cif_tick_series': cif_tick_series,
|
|
3625
3637
|
'cif_hkl_map': cif_hkl_map,
|
|
3626
3638
|
'cif_hkl_label_map': cif_hkl_label_map,
|
|
3627
3639
|
'show_cif_hkl': bool(show_cif_hkl),
|
|
@@ -4457,7 +4469,13 @@ def batplot_main() -> int: # type: ignore
|
|
|
4457
4469
|
draw_cif_ticks()
|
|
4458
4470
|
|
|
4459
4471
|
def draw_cif_ticks():
|
|
4460
|
-
|
|
4472
|
+
# Interactive menu mutates _bp.cif_tick_series; session/menu paths may use a
|
|
4473
|
+
# different list than this closure. fig._batplot_cif_tick_series stays synced
|
|
4474
|
+
# from interactive_menu so redraw sees renames (r→t), reorder, colors, etc.
|
|
4475
|
+
cif_series_draw = getattr(fig, '_batplot_cif_tick_series', None)
|
|
4476
|
+
if cif_series_draw is None:
|
|
4477
|
+
cif_series_draw = cif_tick_series
|
|
4478
|
+
if not cif_series_draw:
|
|
4461
4479
|
return
|
|
4462
4480
|
# Preserve current limits before drawing - use actual current limits
|
|
4463
4481
|
# to prevent any movement when toggling
|
|
@@ -4492,13 +4510,13 @@ def batplot_main() -> int: # type: ignore
|
|
|
4492
4510
|
_bp_module = sys.modules.get('__main__')
|
|
4493
4511
|
if _bp_module is not None and hasattr(_bp_module, 'cif_set_visible'):
|
|
4494
4512
|
vis = list(getattr(_bp_module, 'cif_set_visible') or [])
|
|
4495
|
-
if len(vis) == len(
|
|
4513
|
+
if len(vis) == len(cif_series_draw):
|
|
4496
4514
|
set_visible = [bool(v) for v in vis]
|
|
4497
4515
|
except Exception:
|
|
4498
4516
|
pass
|
|
4499
4517
|
# Effective number of visible CIF rows (for spacing and y-limit expansion)
|
|
4500
4518
|
if set_visible is None:
|
|
4501
|
-
n_rows = len(
|
|
4519
|
+
n_rows = len(cif_series_draw)
|
|
4502
4520
|
else:
|
|
4503
4521
|
n_rows = max(1, sum(1 for v in set_visible if v))
|
|
4504
4522
|
|
|
@@ -4571,7 +4589,7 @@ def batplot_main() -> int: # type: ignore
|
|
|
4571
4589
|
except Exception:
|
|
4572
4590
|
pass
|
|
4573
4591
|
visible_idx = 0
|
|
4574
|
-
for i,(lab, fname, peaksQ, wl, qmax_sim, color) in enumerate(
|
|
4592
|
+
for i,(lab, fname, peaksQ, wl, qmax_sim, color) in enumerate(cif_series_draw):
|
|
4575
4593
|
if set_visible is not None and i < len(set_visible) and not set_visible[i]:
|
|
4576
4594
|
continue
|
|
4577
4595
|
y_line = base - visible_idx * spacing + xy_cif_stack_y_offset(fig, i)
|
|
@@ -4651,7 +4669,7 @@ def batplot_main() -> int: # type: ignore
|
|
|
4651
4669
|
hover_meta = []
|
|
4652
4670
|
show_hkl = globals().get('show_cif_hkl', False)
|
|
4653
4671
|
# Build mapping from Q to label text if available
|
|
4654
|
-
for i,(lab, fname, peaksQ, wl, qmax_sim, color) in enumerate(
|
|
4672
|
+
for i,(lab, fname, peaksQ, wl, qmax_sim, color) in enumerate(cif_series_draw):
|
|
4655
4673
|
if use_2th and wl is None:
|
|
4656
4674
|
wl = getattr(ax, '_cif_hover_wl', None)
|
|
4657
4675
|
# Recreate domain peaks consistent with those drawn (limit to view)
|
|
@@ -4738,6 +4756,10 @@ def batplot_main() -> int: # type: ignore
|
|
|
4738
4756
|
ax._cif_hover_cid = cid
|
|
4739
4757
|
|
|
4740
4758
|
if cif_tick_series:
|
|
4759
|
+
try:
|
|
4760
|
+
fig._batplot_cif_tick_series = cif_tick_series
|
|
4761
|
+
except Exception:
|
|
4762
|
+
pass
|
|
4741
4763
|
# Auto-assign distinct colors for CIF tick series.
|
|
4742
4764
|
# For multiple CIF series:
|
|
4743
4765
|
# - If <= 10 files, use 'tab10' but in a re-ordered sequence to
|
|
@@ -149,21 +149,17 @@ def _legend_no_frame(ax, *args, **kwargs):
|
|
|
149
149
|
kwargs.setdefault('columnspacing', 0.6)
|
|
150
150
|
# Don't use labelcolor='linecolor' by default as it causes issues
|
|
151
151
|
# with scatter plots that have facecolor='none' (hollow markers)
|
|
152
|
-
|
|
152
|
+
legend_host_ax = kwargs.pop('legend_host_ax', None)
|
|
153
|
+
target_ax = legend_host_ax if legend_host_ax is not None else ax
|
|
154
|
+
leg = target_ax.legend(*args, **kwargs)
|
|
153
155
|
if leg is not None:
|
|
154
156
|
try:
|
|
155
157
|
leg.set_frame_on(False)
|
|
158
|
+
# Keep legend above plot artists on the hosting axis.
|
|
159
|
+
leg.set_zorder(1_000_000)
|
|
160
|
+
leg.set_clip_on(False)
|
|
156
161
|
for t in leg.get_texts():
|
|
157
162
|
t.set_verticalalignment('center')
|
|
158
|
-
# Nudge text up so it aligns with the symbol handle (patches sit higher than text baseline)
|
|
159
|
-
try:
|
|
160
|
-
sizes = [t.get_fontsize() for t in leg.get_texts() if t.get_text().strip()]
|
|
161
|
-
fs = float(np.mean(sizes)) if sizes else 10.0
|
|
162
|
-
shift_pts = fs * 0.5 # Points to move text up (was 0.15, increased for proper alignment)
|
|
163
|
-
for t in leg.get_texts():
|
|
164
|
-
t.set_position((0, shift_pts))
|
|
165
|
-
except Exception:
|
|
166
|
-
pass
|
|
167
163
|
except Exception:
|
|
168
164
|
pass
|
|
169
165
|
return leg
|
|
@@ -540,13 +536,16 @@ def _rebuild_legend(ax, ax2, file_data, preserve_position=True):
|
|
|
540
536
|
_legend_no_frame(ax, h_all, l_all, loc='center',
|
|
541
537
|
bbox_to_anchor=(fx, fy),
|
|
542
538
|
bbox_transform=fig.transFigure,
|
|
543
|
-
borderaxespad=1.0, title=leg_title
|
|
539
|
+
borderaxespad=1.0, title=leg_title,
|
|
540
|
+
legend_host_ax=ax2)
|
|
544
541
|
except Exception:
|
|
545
542
|
_legend_no_frame(ax, h_all, l_all, loc='best',
|
|
546
|
-
borderaxespad=1.0, title=leg_title
|
|
543
|
+
borderaxespad=1.0, title=leg_title,
|
|
544
|
+
legend_host_ax=ax2)
|
|
547
545
|
else:
|
|
548
546
|
_legend_no_frame(ax, h_all, l_all, loc='best',
|
|
549
|
-
borderaxespad=1.0, title=leg_title
|
|
547
|
+
borderaxespad=1.0, title=leg_title,
|
|
548
|
+
legend_host_ax=ax2)
|
|
550
549
|
else:
|
|
551
550
|
# Single-file: standard handles from axes
|
|
552
551
|
try:
|
|
@@ -570,13 +569,16 @@ def _rebuild_legend(ax, ax2, file_data, preserve_position=True):
|
|
|
570
569
|
_legend_no_frame(ax, h_all, l_all, loc='center',
|
|
571
570
|
bbox_to_anchor=(fx, fy),
|
|
572
571
|
bbox_transform=fig.transFigure,
|
|
573
|
-
borderaxespad=1.0, title=leg_title
|
|
572
|
+
borderaxespad=1.0, title=leg_title,
|
|
573
|
+
legend_host_ax=ax2)
|
|
574
574
|
except Exception:
|
|
575
575
|
_legend_no_frame(ax, h_all, l_all, loc='best',
|
|
576
|
-
borderaxespad=1.0, title=leg_title
|
|
576
|
+
borderaxespad=1.0, title=leg_title,
|
|
577
|
+
legend_host_ax=ax2)
|
|
577
578
|
else:
|
|
578
579
|
_legend_no_frame(ax, h_all, l_all, loc='best',
|
|
579
|
-
borderaxespad=1.0, title=leg_title
|
|
580
|
+
borderaxespad=1.0, title=leg_title,
|
|
581
|
+
legend_host_ax=ax2)
|
|
580
582
|
else:
|
|
581
583
|
leg = ax.get_legend()
|
|
582
584
|
if leg:
|
|
@@ -654,13 +656,16 @@ def _build_compact_cpc_legend(ax, ax2, file_data, xy_in=None, leg_title=None):
|
|
|
654
656
|
pass
|
|
655
657
|
|
|
656
658
|
# --- Build proxy handles and labels ---
|
|
657
|
-
|
|
658
|
-
#
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
659
|
+
# Use scatter proxy handles so legend uses scatter-specific vertical alignment
|
|
660
|
+
# controls (scatterpoints/scatteryoffsets), which is more stable here.
|
|
661
|
+
ms2 = 28.0 # marker area in points^2
|
|
662
|
+
chg_handle = ax.scatter([], [], marker='s', s=ms2,
|
|
663
|
+
facecolors='#444444', edgecolors='#444444',
|
|
664
|
+
linewidths=1.0, label='Charge')
|
|
665
|
+
dch_handle = ax.scatter([], [], marker='s', s=ms2,
|
|
666
|
+
facecolors='none', edgecolors='#444444',
|
|
667
|
+
linewidths=1.2, label='Discharge')
|
|
668
|
+
handles = [chg_handle, dch_handle]
|
|
664
669
|
labels = ['Charge', 'Discharge']
|
|
665
670
|
|
|
666
671
|
# Header: efficiency triangle if visible
|
|
@@ -680,33 +685,34 @@ def _build_compact_cpc_legend(ax, ax2, file_data, xy_in=None, leg_title=None):
|
|
|
680
685
|
pass
|
|
681
686
|
except Exception:
|
|
682
687
|
eff_color = '#888888'
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
markersize=4, label='Efficiency')
|
|
688
|
+
eff_handle = ax.scatter([], [], marker='^', s=ms2,
|
|
689
|
+
facecolors=eff_color, edgecolors=eff_color,
|
|
690
|
+
linewidths=1.0, label='Efficiency')
|
|
687
691
|
handles.append(eff_handle)
|
|
688
692
|
labels.append('Efficiency')
|
|
689
693
|
|
|
690
|
-
# Separator: invisible
|
|
691
|
-
sep =
|
|
694
|
+
# Separator: invisible marker handle with empty label to create visual gap
|
|
695
|
+
sep = ax.scatter([], [], s=0.0, alpha=0.0, label='')
|
|
692
696
|
handles.append(sep)
|
|
693
697
|
labels.append('')
|
|
694
698
|
|
|
695
699
|
# Per-file rows
|
|
696
700
|
for color, fname in file_rows:
|
|
697
|
-
|
|
698
|
-
|
|
701
|
+
file_handle = ax.scatter([], [], marker='s', s=ms2,
|
|
702
|
+
facecolors=color, edgecolors=color,
|
|
703
|
+
linewidths=1.0, label=fname)
|
|
704
|
+
handles.append(file_handle)
|
|
699
705
|
labels.append(fname)
|
|
700
706
|
|
|
701
707
|
if not file_rows:
|
|
702
708
|
return # Nothing to show
|
|
703
709
|
|
|
704
710
|
fig = ax.figure
|
|
705
|
-
# Multi-file
|
|
711
|
+
# Multi-file compact legend with scatter-specific alignment controls.
|
|
706
712
|
_hl = 0.35
|
|
707
713
|
_legend_kw = dict(
|
|
708
714
|
handlelength=_hl, handleheight=_hl, borderaxespad=1.0, title=leg_title,
|
|
709
|
-
|
|
715
|
+
scatterpoints=1, scatteryoffsets=[0.5],
|
|
710
716
|
)
|
|
711
717
|
if xy_in is not None:
|
|
712
718
|
try:
|
|
@@ -717,11 +723,14 @@ def _build_compact_cpc_legend(ax, ax2, file_data, xy_in=None, leg_title=None):
|
|
|
717
723
|
loc='center',
|
|
718
724
|
bbox_to_anchor=(fx, fy),
|
|
719
725
|
bbox_transform=fig.transFigure,
|
|
726
|
+
legend_host_ax=ax2,
|
|
720
727
|
**_legend_kw)
|
|
721
728
|
except Exception:
|
|
722
|
-
_legend_no_frame(ax, handles, labels, loc='best',
|
|
729
|
+
_legend_no_frame(ax, handles, labels, loc='best',
|
|
730
|
+
legend_host_ax=ax2, **_legend_kw)
|
|
723
731
|
else:
|
|
724
|
-
_legend_no_frame(ax, handles, labels, loc='best',
|
|
732
|
+
_legend_no_frame(ax, handles, labels, loc='best',
|
|
733
|
+
legend_host_ax=ax2, **_legend_kw)
|
|
725
734
|
|
|
726
735
|
|
|
727
736
|
def _get_geometry_snapshot(ax, ax2) -> Dict:
|
|
@@ -177,6 +177,70 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
177
177
|
# Provide a consistent interface for accessing CIF state
|
|
178
178
|
_bp = type('CIFState', (), cif_globals)() if cif_globals else None
|
|
179
179
|
|
|
180
|
+
def _sync_fig_cif_tick_series():
|
|
181
|
+
"""Keep fig._batplot_cif_tick_series aligned with menu state for CIF redraw."""
|
|
182
|
+
if _bp is None:
|
|
183
|
+
return
|
|
184
|
+
try:
|
|
185
|
+
_cts = getattr(_bp, 'cif_tick_series', None)
|
|
186
|
+
if _cts is not None:
|
|
187
|
+
fig._batplot_cif_tick_series = _cts
|
|
188
|
+
except Exception:
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
_sync_fig_cif_tick_series()
|
|
192
|
+
|
|
193
|
+
def _cif_series_for_session():
|
|
194
|
+
"""CIF list for save (s), export (p), and undo snapshot: same as redraw (fig-backed)."""
|
|
195
|
+
try:
|
|
196
|
+
c = getattr(fig, '_batplot_cif_tick_series', None)
|
|
197
|
+
if c is not None:
|
|
198
|
+
return c
|
|
199
|
+
except Exception:
|
|
200
|
+
pass
|
|
201
|
+
if _bp is not None:
|
|
202
|
+
return getattr(_bp, 'cif_tick_series', None)
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
def _print_cif_phase_list(cts):
|
|
206
|
+
for i, (lab, fname, *_rest) in enumerate(cts):
|
|
207
|
+
print(f" {i+1}: {lab} ({os.path.basename(fname)})")
|
|
208
|
+
|
|
209
|
+
def _apply_cif_phase_label_rename(idx: int, new_label: str) -> None:
|
|
210
|
+
"""Update one CIF phase row label and redraw (shared by main r→t and cif→r)."""
|
|
211
|
+
cts = getattr(_bp, 'cif_tick_series', None) if _bp is not None else None
|
|
212
|
+
if not cts or not (0 <= idx < len(cts)):
|
|
213
|
+
return
|
|
214
|
+
try:
|
|
215
|
+
push_state("cif-rename")
|
|
216
|
+
except Exception:
|
|
217
|
+
pass
|
|
218
|
+
_, fname, peaksQ, wl_e, qmax, col = cts[idx]
|
|
219
|
+
if _bp is not None:
|
|
220
|
+
setattr(_bp, 'cif_extend_suspended', True)
|
|
221
|
+
if hasattr(ax, '_cif_tick_art'):
|
|
222
|
+
try:
|
|
223
|
+
for art in list(getattr(ax, '_cif_tick_art', [])):
|
|
224
|
+
try:
|
|
225
|
+
art.remove()
|
|
226
|
+
except Exception:
|
|
227
|
+
pass
|
|
228
|
+
ax._cif_tick_art = []
|
|
229
|
+
except Exception:
|
|
230
|
+
pass
|
|
231
|
+
cts[idx] = (new_label, fname, peaksQ, wl_e, qmax, col)
|
|
232
|
+
if _bp is not None:
|
|
233
|
+
setattr(_bp, 'cif_tick_series', cts)
|
|
234
|
+
_sync_fig_cif_tick_series()
|
|
235
|
+
if hasattr(ax, '_cif_draw_func'):
|
|
236
|
+
ax._cif_draw_func()
|
|
237
|
+
try:
|
|
238
|
+
fig.canvas.draw()
|
|
239
|
+
except Exception:
|
|
240
|
+
pass
|
|
241
|
+
if _bp is not None:
|
|
242
|
+
setattr(_bp, 'cif_extend_suspended', False)
|
|
243
|
+
|
|
180
244
|
try:
|
|
181
245
|
raw_source_paths = list(getattr(args, 'files', []) or [])
|
|
182
246
|
except Exception:
|
|
@@ -1001,7 +1065,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1001
1065
|
|
|
1002
1066
|
# NEW: export current style to .bpcfg
|
|
1003
1067
|
def export_style_config(filename, base_path=None, overwrite_path=None, force_kind=None):
|
|
1004
|
-
cts =
|
|
1068
|
+
cts = _cif_series_for_session()
|
|
1005
1069
|
show_titles = bool(getattr(_bp, 'show_cif_titles', True)) if _bp is not None else True
|
|
1006
1070
|
return _export_style_config(
|
|
1007
1071
|
filename,
|
|
@@ -1023,7 +1087,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1023
1087
|
|
|
1024
1088
|
# NEW: apply imported style config (restricted application)
|
|
1025
1089
|
def apply_style_config(filename):
|
|
1026
|
-
cts =
|
|
1090
|
+
cts = _cif_series_for_session()
|
|
1027
1091
|
hkl_map = getattr(_bp, 'cif_hkl_label_map', None) if _bp is not None else None
|
|
1028
1092
|
res = _bp_apply_style_config(
|
|
1029
1093
|
filename,
|
|
@@ -1042,6 +1106,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1042
1106
|
hkl_map,
|
|
1043
1107
|
adjust_margins,
|
|
1044
1108
|
)
|
|
1109
|
+
_sync_fig_cif_tick_series()
|
|
1045
1110
|
# Sync top/right tick label2 fonts with current rcParams after style import
|
|
1046
1111
|
try:
|
|
1047
1112
|
fam_chain = plt.rcParams.get('font.sans-serif')
|
|
@@ -1602,6 +1667,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1602
1667
|
except Exception:
|
|
1603
1668
|
return None
|
|
1604
1669
|
return None
|
|
1670
|
+
_cts_for_snap = _cif_series_for_session()
|
|
1605
1671
|
snap = {
|
|
1606
1672
|
"note": note,
|
|
1607
1673
|
"xlim": ax.get_xlim(),
|
|
@@ -1638,7 +1704,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1638
1704
|
"tick_direction": getattr(fig, '_tick_direction', 'out'),
|
|
1639
1705
|
"tick_spacing": _capture_tick_spacing(ax),
|
|
1640
1706
|
"tick_minor_count": _capture_tick_minor_count(ax),
|
|
1641
|
-
"cif_tick_series": (list(
|
|
1707
|
+
"cif_tick_series": (list(_cts_for_snap) if _cts_for_snap is not None else None),
|
|
1642
1708
|
"show_cif_hkl": (bool(getattr(_bp, 'show_cif_hkl')) if _bp is not None and hasattr(_bp, 'show_cif_hkl') else False),
|
|
1643
1709
|
"show_cif_titles": (bool(getattr(_bp, 'show_cif_titles')) if _bp is not None and hasattr(_bp, 'show_cif_titles') else True),
|
|
1644
1710
|
"rotation_angle": getattr(ax, '_rotation_angle', 0),
|
|
@@ -2025,6 +2091,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2025
2091
|
_bp.cif_tick_series[:] = [tuple(t) for t in snap["cif_tick_series"]]
|
|
2026
2092
|
except Exception:
|
|
2027
2093
|
pass
|
|
2094
|
+
_sync_fig_cif_tick_series()
|
|
2028
2095
|
if _bp is not None and 'show_cif_hkl' in snap:
|
|
2029
2096
|
try:
|
|
2030
2097
|
new_state = bool(snap['show_cif_hkl'])
|
|
@@ -2160,7 +2227,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2160
2227
|
print(" " + colorize_menu("p: shift all CIF ticks (w/s or type a value)"))
|
|
2161
2228
|
print(" " + colorize_menu("c: CIF color (per set)"))
|
|
2162
2229
|
print(" " + colorize_menu("x: show/hide CIF set"))
|
|
2163
|
-
print(" " + colorize_menu("r: rename CIF
|
|
2230
|
+
print(" " + colorize_menu("r: rename CIF phase label (same as main menu r→t)"))
|
|
2164
2231
|
print(" " + colorize_menu("q: back to main menu"))
|
|
2165
2232
|
sub = _safe_input(colorize_prompt("CIF (z/t/v/p/c/x/r/q): ")).strip().lower()
|
|
2166
2233
|
if not sub or sub == 'q':
|
|
@@ -2265,6 +2332,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2265
2332
|
new_cts = [cts[i - 1] for i in parts]
|
|
2266
2333
|
if _bp is not None:
|
|
2267
2334
|
setattr(_bp, 'cif_tick_series', new_cts)
|
|
2335
|
+
_sync_fig_cif_tick_series()
|
|
2268
2336
|
prev_offs = getattr(fig, '_bp_cif_stack_y_offsets', None)
|
|
2269
2337
|
if prev_offs is not None and len(prev_offs) == len(cts):
|
|
2270
2338
|
fig._bp_cif_stack_y_offsets = [prev_offs[i - 1] for i in parts]
|
|
@@ -2406,6 +2474,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2406
2474
|
cts[idx] = (lab, fname, peaksQ, wl_e, qmax, resolved)
|
|
2407
2475
|
if _bp is not None:
|
|
2408
2476
|
setattr(_bp, 'cif_tick_series', cts)
|
|
2477
|
+
_sync_fig_cif_tick_series()
|
|
2409
2478
|
if hasattr(ax, '_cif_draw_func'):
|
|
2410
2479
|
ax._cif_draw_func()
|
|
2411
2480
|
else:
|
|
@@ -2511,6 +2580,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2511
2580
|
cts[idx] = (lab, fname, peaksQ, wl_e, qmax, col_val)
|
|
2512
2581
|
if _bp is not None:
|
|
2513
2582
|
setattr(_bp, 'cif_tick_series', cts)
|
|
2583
|
+
_sync_fig_cif_tick_series()
|
|
2514
2584
|
if hasattr(ax, '_cif_draw_func'):
|
|
2515
2585
|
ax._cif_draw_func()
|
|
2516
2586
|
except Exception as e:
|
|
@@ -2558,39 +2628,40 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2558
2628
|
except Exception as e:
|
|
2559
2629
|
print(f"Error toggling CIF visibility: {e}")
|
|
2560
2630
|
elif sub == 'r':
|
|
2561
|
-
# Rename CIF
|
|
2631
|
+
# Rename CIF phase labels — same behavior as main menu r→t.
|
|
2562
2632
|
try:
|
|
2563
2633
|
cts = getattr(_bp, 'cif_tick_series', None) if _bp is not None else None
|
|
2564
2634
|
if not cts:
|
|
2565
|
-
print("No CIF
|
|
2635
|
+
print("No CIF phases to rename.")
|
|
2566
2636
|
else:
|
|
2567
2637
|
while True:
|
|
2568
|
-
print("CIF
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2638
|
+
print("CIF phases (q=back to CIF menu)")
|
|
2639
|
+
_print_cif_phase_list(cts)
|
|
2640
|
+
idx_s = _safe_input(
|
|
2641
|
+
"Phase number to rename (q=back): "
|
|
2642
|
+
).strip().lower()
|
|
2572
2643
|
if not idx_s or idx_s == 'q':
|
|
2573
2644
|
break
|
|
2574
2645
|
try:
|
|
2575
2646
|
idx = int(idx_s) - 1
|
|
2576
|
-
if 0 <= idx < len(cts):
|
|
2577
|
-
lab, fname, peaksQ, wl_e, qmax, col = cts[idx]
|
|
2578
|
-
new_lab = _safe_input(f"New label for set {idx+1} (current: {lab}, blank=cancel): ").strip()
|
|
2579
|
-
if not new_lab:
|
|
2580
|
-
continue
|
|
2581
|
-
push_state("cif-rename")
|
|
2582
|
-
cts[idx] = (new_lab, fname, peaksQ, wl_e, qmax, col)
|
|
2583
|
-
if _bp is not None:
|
|
2584
|
-
setattr(_bp, 'cif_tick_series', cts)
|
|
2585
|
-
if hasattr(ax, '_cif_draw_func'):
|
|
2586
|
-
ax._cif_draw_func()
|
|
2587
|
-
print(f"Set {idx+1} renamed to: {new_lab}")
|
|
2588
|
-
else:
|
|
2647
|
+
if not (0 <= idx < len(cts)):
|
|
2589
2648
|
print("Invalid index.")
|
|
2649
|
+
continue
|
|
2590
2650
|
except ValueError:
|
|
2591
2651
|
print("Invalid index.")
|
|
2652
|
+
continue
|
|
2653
|
+
print_label_latex_tips()
|
|
2654
|
+
new_lab = _safe_input(
|
|
2655
|
+
"New CIF phase label (q=cancel): "
|
|
2656
|
+
).strip()
|
|
2657
|
+
if not new_lab or new_lab.lower() == 'q':
|
|
2658
|
+
print("Canceled.")
|
|
2659
|
+
continue
|
|
2660
|
+
new_lab = convert_label_shortcuts(new_lab)
|
|
2661
|
+
_apply_cif_phase_label_rename(idx, new_lab)
|
|
2662
|
+
print(f"Phase {idx + 1} label updated.")
|
|
2592
2663
|
except Exception as e:
|
|
2593
|
-
print(f"Error renaming CIF
|
|
2664
|
+
print(f"Error renaming CIF phase labels: {e}")
|
|
2594
2665
|
else:
|
|
2595
2666
|
print("Unknown option.")
|
|
2596
2667
|
continue
|
|
@@ -2724,7 +2795,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2724
2795
|
delta=delta,
|
|
2725
2796
|
args=args,
|
|
2726
2797
|
tick_state=tick_state,
|
|
2727
|
-
cif_tick_series=(
|
|
2798
|
+
cif_tick_series=_cif_series_for_session(),
|
|
2728
2799
|
cif_hkl_map=(getattr(_bp, 'cif_hkl_map', None) if _bp is not None else None),
|
|
2729
2800
|
cif_hkl_label_map=(getattr(_bp, 'cif_hkl_label_map', None) if _bp is not None else None),
|
|
2730
2801
|
show_cif_hkl=(bool(getattr(_bp, 'show_cif_hkl', False)) if _bp is not None else False),
|
|
@@ -2886,7 +2957,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2886
2957
|
delta=delta,
|
|
2887
2958
|
args=args,
|
|
2888
2959
|
tick_state=tick_state,
|
|
2889
|
-
cif_tick_series=(
|
|
2960
|
+
cif_tick_series=_cif_series_for_session(),
|
|
2890
2961
|
cif_hkl_map=(getattr(_bp, 'cif_hkl_map', None) if _bp is not None else None),
|
|
2891
2962
|
cif_hkl_label_map=(getattr(_bp, 'cif_hkl_label_map', None) if _bp is not None else None),
|
|
2892
2963
|
show_cif_hkl=(bool(getattr(_bp,'show_cif_hkl', False)) if _bp is not None else False),
|
|
@@ -2919,7 +2990,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2919
2990
|
delta=delta,
|
|
2920
2991
|
args=args,
|
|
2921
2992
|
tick_state=tick_state,
|
|
2922
|
-
cif_tick_series=(
|
|
2993
|
+
cif_tick_series=_cif_series_for_session(),
|
|
2923
2994
|
cif_hkl_map=(getattr(_bp, 'cif_hkl_map', None) if _bp is not None else None),
|
|
2924
2995
|
cif_hkl_label_map=(getattr(_bp, 'cif_hkl_label_map', None) if _bp is not None else None),
|
|
2925
2996
|
show_cif_hkl=(bool(getattr(_bp,'show_cif_hkl', False)) if _bp is not None else False),
|
|
@@ -2959,7 +3030,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2959
3030
|
delta=delta,
|
|
2960
3031
|
args=args,
|
|
2961
3032
|
tick_state=tick_state,
|
|
2962
|
-
cif_tick_series=(
|
|
3033
|
+
cif_tick_series=_cif_series_for_session(),
|
|
2963
3034
|
cif_hkl_map=(getattr(_bp, 'cif_hkl_map', None) if _bp is not None else None),
|
|
2964
3035
|
cif_hkl_label_map=(getattr(_bp, 'cif_hkl_label_map', None) if _bp is not None else None),
|
|
2965
3036
|
show_cif_hkl=(bool(getattr(_bp,'show_cif_hkl', False)) if _bp is not None else False),
|
|
@@ -3173,6 +3244,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
3173
3244
|
cts[idx] = (lab, fname, peaksQ, wl_e, qmax, resolved)
|
|
3174
3245
|
if _bp is not None:
|
|
3175
3246
|
setattr(_bp, 'cif_tick_series', cts)
|
|
3247
|
+
_sync_fig_cif_tick_series()
|
|
3176
3248
|
if hasattr(ax, '_cif_draw_func'):
|
|
3177
3249
|
ax._cif_draw_func()
|
|
3178
3250
|
else:
|
|
@@ -3241,6 +3313,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
3241
3313
|
cts[idx] = (lab, fname, peaksQ, wl_e, qmax, col_val)
|
|
3242
3314
|
if _bp is not None:
|
|
3243
3315
|
setattr(_bp, 'cif_tick_series', cts)
|
|
3316
|
+
_sync_fig_cif_tick_series()
|
|
3244
3317
|
if hasattr(ax, '_cif_draw_func'):
|
|
3245
3318
|
ax._cif_draw_func()
|
|
3246
3319
|
else:
|
|
@@ -3333,7 +3406,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
3333
3406
|
while True:
|
|
3334
3407
|
rename_opts = "c=curve"
|
|
3335
3408
|
if has_cif:
|
|
3336
|
-
rename_opts += ", t=
|
|
3409
|
+
rename_opts += ", t=CIF phase label (same as cif→r)"
|
|
3337
3410
|
rename_opts += ", x=x-axis, y=y-axis, q=return"
|
|
3338
3411
|
mode = _safe_input(f"Rename ({rename_opts}): ").strip().lower()
|
|
3339
3412
|
if mode == 'q':
|
|
@@ -3366,44 +3439,34 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
3366
3439
|
elif mode == 't':
|
|
3367
3440
|
cts = getattr(_bp, 'cif_tick_series', None) if _bp is not None else None
|
|
3368
3441
|
if not cts:
|
|
3369
|
-
print("No CIF
|
|
3442
|
+
print("No CIF phases to rename.")
|
|
3443
|
+
continue
|
|
3444
|
+
print("CIF phases (then pick one; same list as cif→r)")
|
|
3445
|
+
_print_cif_phase_list(cts)
|
|
3446
|
+
s = _safe_input(
|
|
3447
|
+
"Phase number to rename (q=cancel): "
|
|
3448
|
+
).strip()
|
|
3449
|
+
if not s or s.lower() == 'q':
|
|
3450
|
+
print("Canceled.")
|
|
3370
3451
|
continue
|
|
3371
|
-
for i,(lab, fname, *_rest) in enumerate(cts):
|
|
3372
|
-
print(f" {i+1}: {lab} ({os.path.basename(fname)})")
|
|
3373
|
-
s = _safe_input("CIF tick number to rename (q=cancel): ").strip()
|
|
3374
|
-
if not s or s.lower()=='q':
|
|
3375
|
-
print("Canceled."); continue
|
|
3376
3452
|
try:
|
|
3377
|
-
idx = int(s)-1
|
|
3453
|
+
idx = int(s) - 1
|
|
3378
3454
|
if not (0 <= idx < len(cts)):
|
|
3379
|
-
print("Index out of range.")
|
|
3455
|
+
print("Index out of range.")
|
|
3456
|
+
continue
|
|
3380
3457
|
except ValueError:
|
|
3381
|
-
print("Bad index.")
|
|
3458
|
+
print("Bad index.")
|
|
3459
|
+
continue
|
|
3382
3460
|
print_label_latex_tips()
|
|
3383
|
-
new_name = _safe_input(
|
|
3384
|
-
|
|
3385
|
-
|
|
3461
|
+
new_name = _safe_input(
|
|
3462
|
+
"New CIF phase label (q=cancel): "
|
|
3463
|
+
).strip()
|
|
3464
|
+
if not new_name or new_name.lower() == 'q':
|
|
3465
|
+
print("Canceled.")
|
|
3466
|
+
continue
|
|
3386
3467
|
new_name = convert_label_shortcuts(new_name)
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
if _bp is not None:
|
|
3390
|
-
setattr(_bp, 'cif_extend_suspended', True)
|
|
3391
|
-
if hasattr(ax, '_cif_tick_art'):
|
|
3392
|
-
try:
|
|
3393
|
-
for art in list(getattr(ax, '_cif_tick_art', [])):
|
|
3394
|
-
try:
|
|
3395
|
-
art.remove()
|
|
3396
|
-
except Exception:
|
|
3397
|
-
pass
|
|
3398
|
-
ax._cif_tick_art = []
|
|
3399
|
-
except Exception:
|
|
3400
|
-
pass
|
|
3401
|
-
cts[idx] = (new_name, fname, peaksQ, wl, qmax_sim, color)
|
|
3402
|
-
setattr(_bp, 'cif_tick_series', cts)
|
|
3403
|
-
if hasattr(ax,'_cif_draw_func'): ax._cif_draw_func()
|
|
3404
|
-
fig.canvas.draw()
|
|
3405
|
-
if _bp is not None:
|
|
3406
|
-
setattr(_bp, 'cif_extend_suspended', False)
|
|
3468
|
+
_apply_cif_phase_label_rename(idx, new_name)
|
|
3469
|
+
print(f"Phase {idx + 1} label updated.")
|
|
3407
3470
|
elif mode in ('x','y'):
|
|
3408
3471
|
print("Enter new axis label (q=cancel).")
|
|
3409
3472
|
print_label_latex_tips()
|
|
@@ -607,66 +607,73 @@ def plot_operando_folder(folder: str, args, cif_files=None) -> Tuple[plt.Figure,
|
|
|
607
607
|
|
|
608
608
|
for ec_path in ec_files:
|
|
609
609
|
is_datalogger = ec_path.suffix.lower() == ".csv" and is_biologic_datalogger_csv(str(ec_path))
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
610
|
+
try:
|
|
611
|
+
if is_datalogger:
|
|
612
|
+
time_h, voltage_v = read_biologic_datalogger_time_voltage(str(ec_path))
|
|
613
|
+
time_h = np.asarray(time_h, float)
|
|
614
|
+
voltage_v = np.asarray(voltage_v, float)
|
|
615
|
+
if len(time_h) == 0 or len(voltage_v) == 0:
|
|
616
|
+
raise ValueError(f"DataLogger file {ec_path.name} has no valid data rows")
|
|
617
|
+
if time_offset != 0:
|
|
618
|
+
time_h = time_h + time_offset
|
|
618
619
|
time_offset = float(np.nanmax(time_h))
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
else:
|
|
622
|
-
# .mpt file
|
|
623
|
-
readcol_mpt = None
|
|
624
|
-
if hasattr(args, 'readcols') and args.readcols is not None:
|
|
625
|
-
readcol_mpt = tuple(args.readcols)
|
|
626
|
-
if readcol_mpt is None and hasattr(args, 'readcol_by_ext') and '.mpt' in getattr(args, 'readcol_by_ext', {}):
|
|
627
|
-
readcol_mpt = args.readcol_by_ext['.mpt']
|
|
628
|
-
|
|
629
|
-
if readcol_mpt:
|
|
630
|
-
data = robust_loadtxt_skipheader(str(ec_path))
|
|
631
|
-
if data.ndim == 1:
|
|
632
|
-
data = data.reshape(1, -1)
|
|
633
|
-
if data.shape[1] < 2:
|
|
634
|
-
raise ValueError(f"MPT file {ec_path.name} has insufficient columns")
|
|
635
|
-
x_col, y_col = readcol_mpt
|
|
636
|
-
x_col_idx, y_col_idx = x_col - 1, y_col - 1
|
|
637
|
-
# EC panel: voltage on X, time on Y; assume x_col=time, y_col=voltage (EC-Lab order)
|
|
638
|
-
v_raw = np.asarray(data[:, y_col_idx], float)
|
|
639
|
-
t_raw = np.asarray(data[:, x_col_idx], float)
|
|
620
|
+
x_parts.append(voltage_v)
|
|
621
|
+
y_parts.append(time_h)
|
|
640
622
|
else:
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
623
|
+
# .mpt file
|
|
624
|
+
readcol_mpt = None
|
|
625
|
+
if hasattr(args, 'readcols') and args.readcols is not None:
|
|
626
|
+
readcol_mpt = tuple(args.readcols)
|
|
627
|
+
if readcol_mpt is None and hasattr(args, 'readcol_by_ext') and '.mpt' in getattr(args, 'readcol_by_ext', {}):
|
|
628
|
+
readcol_mpt = args.readcol_by_ext['.mpt']
|
|
629
|
+
|
|
630
|
+
if readcol_mpt:
|
|
631
|
+
data = robust_loadtxt_skipheader(str(ec_path))
|
|
632
|
+
if data.ndim == 1:
|
|
633
|
+
data = data.reshape(1, -1)
|
|
634
|
+
if data.shape[1] < 2:
|
|
635
|
+
raise ValueError(f"MPT file {ec_path.name} has insufficient columns")
|
|
636
|
+
x_col, y_col = readcol_mpt
|
|
637
|
+
x_col_idx, y_col_idx = x_col - 1, y_col - 1
|
|
638
|
+
# EC panel: voltage on X, time on Y; assume x_col=time, y_col=voltage (EC-Lab order)
|
|
639
|
+
v_raw = np.asarray(data[:, y_col_idx], float)
|
|
640
|
+
t_raw = np.asarray(data[:, x_col_idx], float)
|
|
641
|
+
else:
|
|
642
|
+
result = read_mpt_file(str(ec_path), mode='time')
|
|
643
|
+
if len(result) == 5:
|
|
644
|
+
x_data, y_data, current_mA, x_lbl, y_lbl = result
|
|
645
|
+
x_lower = x_lbl.lower().replace(' ', '').replace('_', '')
|
|
646
|
+
y_lower = y_lbl.lower().replace(' ', '').replace('_', '')
|
|
647
|
+
has_time_in_x = 'time' in x_lower
|
|
648
|
+
has_voltage_in_y = 'voltage' in y_lower or 'potential' in y_lower or 'ewe' in y_lower
|
|
649
|
+
if x_lbl == 'Time (h)' and y_lbl == 'Potential (V)':
|
|
650
|
+
t_raw = np.asarray(x_data, float)
|
|
651
|
+
v_raw = np.asarray(y_data, float)
|
|
652
|
+
elif has_time_in_x and has_voltage_in_y:
|
|
653
|
+
t_raw = np.asarray(x_data, float)
|
|
654
|
+
v_raw = np.asarray(y_data, float)
|
|
655
|
+
elif 'voltage' in x_lower or 'potential' in x_lower:
|
|
656
|
+
v_raw = np.asarray(x_data, float)
|
|
657
|
+
t_raw = np.asarray(y_data, float)
|
|
658
|
+
else:
|
|
659
|
+
v_raw = np.asarray(x_data, float)
|
|
660
|
+
t_raw = np.asarray(y_data, float)
|
|
657
661
|
else:
|
|
662
|
+
x_data, y_data, current_mA, *_ = result
|
|
658
663
|
v_raw = np.asarray(x_data, float)
|
|
659
|
-
t_raw = np.asarray(y_data, float)
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
t_raw =
|
|
664
|
-
if time_offset != 0:
|
|
665
|
-
t_raw = t_raw + time_offset
|
|
666
|
-
if len(t_raw) > 0:
|
|
664
|
+
t_raw = np.asarray(y_data, float) / 3600.0
|
|
665
|
+
if len(t_raw) == 0 or len(v_raw) == 0:
|
|
666
|
+
raise ValueError(f"MPT file {ec_path.name} has no valid data rows")
|
|
667
|
+
if time_offset != 0:
|
|
668
|
+
t_raw = t_raw + time_offset
|
|
667
669
|
time_offset = float(np.nanmax(t_raw))
|
|
668
|
-
|
|
669
|
-
|
|
670
|
+
x_parts.append(v_raw)
|
|
671
|
+
y_parts.append(t_raw)
|
|
672
|
+
except Exception as ec_file_err:
|
|
673
|
+
print(f"[operando] Skip EC file {ec_path.name}: {ec_file_err}")
|
|
674
|
+
|
|
675
|
+
if not x_parts or not y_parts:
|
|
676
|
+
raise ValueError("No valid electrochem data points in detected EC files")
|
|
670
677
|
|
|
671
678
|
x_data = np.concatenate(x_parts) if len(x_parts) > 1 else x_parts[0]
|
|
672
679
|
y_data = np.concatenate(y_parts) if len(y_parts) > 1 else y_parts[0]
|
|
@@ -534,6 +534,17 @@ def print_style_info(
|
|
|
534
534
|
if cif_tick_series:
|
|
535
535
|
print(f"\n--- CIF (cif key) ---")
|
|
536
536
|
try:
|
|
537
|
+
print("CIF phase labels (r→t / cif→r) & per-set colors (c); stored in p / i / s / b:")
|
|
538
|
+
for i, ent in enumerate(cif_tick_series):
|
|
539
|
+
if len(ent) < 6:
|
|
540
|
+
continue
|
|
541
|
+
lab, fname, _pq, _wl, _qm, col = ent[0], ent[1], ent[2], ent[3], ent[4], ent[5]
|
|
542
|
+
try:
|
|
543
|
+
ch = mcolors.to_hex(mcolors.to_rgba(col))
|
|
544
|
+
except Exception:
|
|
545
|
+
ch = str(col)
|
|
546
|
+
hb = color_block(ch) if ch else ""
|
|
547
|
+
print(f" {i + 1}: {lab} ({os.path.basename(fname)}) {hb} {ch}")
|
|
537
548
|
hkl_state = None
|
|
538
549
|
_bp_module = sys.modules.get('__main__')
|
|
539
550
|
if _bp_module is not None and hasattr(_bp_module, 'show_cif_hkl'):
|
|
@@ -786,8 +797,9 @@ def export_style_config(
|
|
|
786
797
|
except Exception:
|
|
787
798
|
pass
|
|
788
799
|
if cif_tick_series:
|
|
800
|
+
# label + color so export (p) / import (i) match session (s) / undo (b) for renamed phases
|
|
789
801
|
cfg["cif_ticks"] = [
|
|
790
|
-
{"index": i, "color": color}
|
|
802
|
+
{"index": i, "label": str(lab), "color": color}
|
|
791
803
|
for i, (lab, fname, peaksQ, wl, qmax_sim, color) in enumerate(cif_tick_series)
|
|
792
804
|
]
|
|
793
805
|
# Always save one offset per CIF set so import (i) matches session/undo (s/b).
|
|
@@ -101,10 +101,10 @@ def _wrap_line(text: str, width: int) -> List[str]:
|
|
|
101
101
|
UPDATE_INFO = {
|
|
102
102
|
# Custom message to include in update notification
|
|
103
103
|
# (Auto-filled from RELEASE_NOTES.txt when using batplot --dev-upgrade)
|
|
104
|
-
'custom_message': '- Bug fixes',
|
|
104
|
+
'custom_message': '- Bug fixes for BatX operando plot',
|
|
105
105
|
# Additional notes (auto-filled from RELEASE_NOTES.txt)
|
|
106
106
|
'update_notes': [
|
|
107
|
-
'- Bug fixes'
|
|
107
|
+
'- Bug fixes for BatX operando plot'
|
|
108
108
|
],
|
|
109
109
|
'show_update_notes': True,
|
|
110
110
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: batplot
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.39
|
|
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: MIT License
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "batplot"
|
|
7
|
-
version = "1.8.
|
|
7
|
+
version = "1.8.39"
|
|
8
8
|
description = "Interactive plotting tool for material science (1D plot) and electrochemistry (GC, CV, dQ/dV, CPC, operando) with batch processing"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Tian Dai", email = "tianda@uio.no" }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|