batplot 1.8.35__tar.gz → 1.8.37__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.35/batplot.egg-info → batplot-1.8.37}/PKG-INFO +1 -1
- {batplot-1.8.35 → batplot-1.8.37}/batplot/__init__.py +1 -1
- {batplot-1.8.35 → batplot-1.8.37}/batplot/batch.py +4 -1
- {batplot-1.8.35 → batplot-1.8.37}/batplot/batplot.py +113 -81
- {batplot-1.8.35 → batplot-1.8.37}/batplot/data/CHANGELOG.md +8 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/interactive.py +95 -1
- {batplot-1.8.35 → batplot-1.8.37}/batplot/session.py +71 -22
- {batplot-1.8.35 → batplot-1.8.37}/batplot/style.py +51 -7
- {batplot-1.8.35 → batplot-1.8.37}/batplot/utils.py +121 -0
- {batplot-1.8.35 → batplot-1.8.37/batplot.egg-info}/PKG-INFO +1 -1
- {batplot-1.8.35 → batplot-1.8.37}/pyproject.toml +1 -1
- {batplot-1.8.35 → batplot-1.8.37}/LICENSE +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/MANIFEST.in +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/NOTICE +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/README.md +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/args.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/canvas_interactive.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/cif.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/cli.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/color_utils.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/config.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/converters.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/cpc_interactive.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/data/USER_MANUAL.md +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/dev_upgrade.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/electrochem_interactive.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/manual.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/modes.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/operando.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/operando_ec_interactive.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/plotting.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/readers.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/showcol.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/ui.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot/version_check.py +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot.egg-info/SOURCES.txt +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot.egg-info/dependency_links.txt +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot.egg-info/entry_points.txt +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot.egg-info/requires.txt +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/batplot.egg-info/top_level.txt +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/setup.cfg +0 -0
- {batplot-1.8.35 → batplot-1.8.37}/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.37
|
|
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
|
|
@@ -703,8 +703,11 @@ def batch_process(directory: str, args):
|
|
|
703
703
|
if ext and ext not in args._batch_warned_extensions and ext not in known_axis_ext:
|
|
704
704
|
args._batch_warned_extensions.add(ext)
|
|
705
705
|
print(f" Note: Reading '{ext}' files as 2-column (x, y) data with x-axis = {args.xaxis}")
|
|
706
|
+
elif getattr(args, 'wl', None) is not None:
|
|
707
|
+
# .txt / generic text: --wl implies XRD Q conversion (matches main batplot.py)
|
|
708
|
+
axis_mode = 'Q'
|
|
706
709
|
else:
|
|
707
|
-
raise ValueError(f"Unknown file type: {fname}. Use --xaxis [Q|2theta|r|k|energy|rft] or batplot --help for help.")
|
|
710
|
+
raise ValueError(f"Unknown file type: {fname}. Use --xaxis [Q|2theta|r|k|energy|rft], or --wl for XRD Q, or batplot --help for help.")
|
|
708
711
|
|
|
709
712
|
# Convert to Q if needed
|
|
710
713
|
if axis_mode == 'Q' and ext not in ('.qye', '.gr', '.nor'):
|
|
@@ -23,7 +23,17 @@ from .session import (
|
|
|
23
23
|
)
|
|
24
24
|
from .operando import plot_operando_folder
|
|
25
25
|
from .plotting import update_labels
|
|
26
|
-
from .utils import
|
|
26
|
+
from .utils import (
|
|
27
|
+
_confirm_overwrite,
|
|
28
|
+
normalize_label_text,
|
|
29
|
+
natural_sort_key,
|
|
30
|
+
ensure_subdirectory,
|
|
31
|
+
xy_cif_stack_y_offset,
|
|
32
|
+
xy_cif_tick_stack_layout,
|
|
33
|
+
xy_cif_add_phase_title,
|
|
34
|
+
xy_cif_row_spacing_yr,
|
|
35
|
+
xy_cif_stack_bottom_margin_yr,
|
|
36
|
+
)
|
|
27
37
|
from .readers import (
|
|
28
38
|
read_csv_file,
|
|
29
39
|
read_fullprof_rowwise,
|
|
@@ -3411,25 +3421,30 @@ def batplot_main() -> int: # type: ignore
|
|
|
3411
3421
|
except Exception:
|
|
3412
3422
|
pass
|
|
3413
3423
|
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
if saved_stack or len(y_data_list) > 1:
|
|
3424
|
+
_stacked_s = bool(saved_stack or len(y_data_list) > 1)
|
|
3425
|
+
if _stacked_s:
|
|
3417
3426
|
global_min = min(float(a.min()) for a in y_data_list if len(a)) if y_data_list else fixed_ylim[0]
|
|
3418
|
-
base = global_min - 0.08*
|
|
3427
|
+
base = global_min - 0.08 * fixed_yr
|
|
3419
3428
|
else:
|
|
3420
3429
|
global_min = min(float(a.min()) for a in y_data_list if len(a)) if y_data_list else 0.0
|
|
3421
|
-
base = global_min - 0.06*
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3430
|
+
base = global_min - 0.06 * fixed_yr
|
|
3431
|
+
spacing = xy_cif_row_spacing_yr(
|
|
3432
|
+
fixed_yr,
|
|
3433
|
+
show_titles=show_titles_local,
|
|
3434
|
+
show_hkl=show_hkl_local,
|
|
3435
|
+
stacked_or_multi_y=_stacked_s,
|
|
3436
|
+
)
|
|
3437
|
+
_cif_bottom_m = xy_cif_stack_bottom_margin_yr(fixed_yr, show_titles=show_titles_local)
|
|
3438
|
+
needed_min = base - (len(cif_tick_series) - 1) * spacing - _cif_bottom_m
|
|
3439
|
+
if not show_titles_local:
|
|
3440
|
+
ylim_draw = tuple(prev_ylim)
|
|
3441
|
+
elif needed_min >= prev_ylim[0]:
|
|
3442
|
+
ylim_draw = tuple(prev_ylim)
|
|
3428
3443
|
else:
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3444
|
+
new_ymin = min(needed_min, prev_ylim[0])
|
|
3445
|
+
ylim_draw = (new_ymin, prev_ylim[1])
|
|
3446
|
+
ax.set_ylim(ylim_draw)
|
|
3447
|
+
|
|
3433
3448
|
cur_ylim = ax.get_ylim()
|
|
3434
3449
|
yr = cur_ylim[1] - cur_ylim[0]
|
|
3435
3450
|
if yr <= 0: yr = 1.0
|
|
@@ -3443,7 +3458,8 @@ def batplot_main() -> int: # type: ignore
|
|
|
3443
3458
|
|
|
3444
3459
|
# Draw each series
|
|
3445
3460
|
for i,(lab,fname,peaksQ,wl,qmax_sim,color) in enumerate(cif_tick_series):
|
|
3446
|
-
y_line = base - i*spacing
|
|
3461
|
+
y_line = base - i * spacing + xy_cif_stack_y_offset(fig, i)
|
|
3462
|
+
tick_h, hkl_y = xy_cif_tick_stack_layout(y_line, yr)
|
|
3447
3463
|
# Convert peaks to axis domain
|
|
3448
3464
|
if use_2th:
|
|
3449
3465
|
wl_use = wl if wl is not None else wl_any
|
|
@@ -3463,7 +3479,7 @@ def batplot_main() -> int: # type: ignore
|
|
|
3463
3479
|
label_map = {} # Clear label map if too many peaks
|
|
3464
3480
|
for p in domain_peaks:
|
|
3465
3481
|
# Use color from tuple (preserved from session)
|
|
3466
|
-
ln, = ax.plot([p,p],[y_line, y_line+
|
|
3482
|
+
ln, = ax.plot([p, p], [y_line, y_line + tick_h], color=color, lw=1.0, alpha=0.9, zorder=3)
|
|
3467
3483
|
new_art.append(ln)
|
|
3468
3484
|
# Only show hkl labels if explicitly enabled
|
|
3469
3485
|
if show_hkl_local:
|
|
@@ -3476,32 +3492,17 @@ def batplot_main() -> int: # type: ignore
|
|
|
3476
3492
|
Qp_rounded = round(Qp, 6)
|
|
3477
3493
|
lbl = label_map.get(Qp_rounded)
|
|
3478
3494
|
if lbl:
|
|
3479
|
-
|
|
3480
|
-
t_hkl = ax.text(p, y_line+0.022*yr, lbl, ha='center', va='bottom', fontsize=7, rotation=90, color=color)
|
|
3495
|
+
t_hkl = ax.text(p, hkl_y, lbl, ha='center', va='bottom', fontsize=7, rotation=90, color=color)
|
|
3481
3496
|
new_art.append(t_hkl)
|
|
3482
3497
|
# Only add title label if show_cif_titles is True
|
|
3483
3498
|
if show_titles_local:
|
|
3484
3499
|
label_text = f" {lab}"
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3500
|
+
xy_cif_add_phase_title(
|
|
3501
|
+
ax, prev_xlim[0], y_line, tick_h, label_text,
|
|
3502
|
+
max(8, int(0.55 * plt.rcParams.get('font.size', 16))), color, new_art,
|
|
3503
|
+
)
|
|
3489
3504
|
ax._cif_tick_art = new_art
|
|
3490
|
-
# Restore x-axis limits
|
|
3491
3505
|
ax.set_xlim(prev_xlim)
|
|
3492
|
-
# Restore y-axis: if titles are hidden, always restore; if titles are shown, only restore if we didn't need to expand
|
|
3493
|
-
# Use prev_ylim (current limits before drawing) to prevent any movement
|
|
3494
|
-
if not show_titles_local:
|
|
3495
|
-
# Titles hidden: always restore original limits
|
|
3496
|
-
ax.set_ylim(prev_ylim)
|
|
3497
|
-
elif needed_min >= prev_ylim[0]:
|
|
3498
|
-
# Titles shown but no expansion needed: restore original limits
|
|
3499
|
-
ax.set_ylim(prev_ylim)
|
|
3500
|
-
else:
|
|
3501
|
-
# Expansion needed: use the minimum of needed_min and prev_ylim[0] to prevent incremental growth
|
|
3502
|
-
# This ensures that repeated toggles don't cause drift
|
|
3503
|
-
new_ymin = min(needed_min, prev_ylim[0])
|
|
3504
|
-
ax.set_ylim(new_ymin, prev_ylim[1])
|
|
3505
3506
|
fig.canvas.draw_idle()
|
|
3506
3507
|
except Exception:
|
|
3507
3508
|
pass
|
|
@@ -3732,12 +3733,17 @@ def batplot_main() -> int: # type: ignore
|
|
|
3732
3733
|
elif any_chir:
|
|
3733
3734
|
axis_mode = "rft"
|
|
3734
3735
|
elif any_txt:
|
|
3735
|
-
# .txt is generic
|
|
3736
|
+
# .txt is generic: need --xaxis unless XRD context is clear from --wl or file:wl (same as .xy without ext hint).
|
|
3736
3737
|
if args.xaxis:
|
|
3737
3738
|
# Normalize case: 'q' or 'Q' → 'Q' (uppercase), everything else lowercase
|
|
3738
3739
|
axis_mode = "Q" if args.xaxis.upper() == "Q" else args.xaxis.lower()
|
|
3740
|
+
elif getattr(args, 'wl', None) is not None or any_lambda:
|
|
3741
|
+
axis_mode = "Q"
|
|
3739
3742
|
else:
|
|
3740
|
-
raise ValueError(
|
|
3743
|
+
raise ValueError(
|
|
3744
|
+
"Unknown file type for .txt. Add --xaxis [Q|2theta|r|k|energy|rft], or use --wl for XRD Q conversion, "
|
|
3745
|
+
"or batplot --help."
|
|
3746
|
+
)
|
|
3741
3747
|
elif any_lambda or any_cif or any_xrd_vendor:
|
|
3742
3748
|
# XRD vendor formats (.raw, .brml, .xrdml, .rasx) are 2theta; CIF is Q; file:wl implies Q domain
|
|
3743
3749
|
if args.xaxis and args.xaxis.lower() in ("2theta","two_theta","tth"):
|
|
@@ -4496,25 +4502,50 @@ def batplot_main() -> int: # type: ignore
|
|
|
4496
4502
|
else:
|
|
4497
4503
|
n_rows = max(1, sum(1 for v in set_visible if v))
|
|
4498
4504
|
|
|
4505
|
+
show_hkl_for_spacing = False
|
|
4506
|
+
try:
|
|
4507
|
+
_bp_module_sp = sys.modules.get('__main__')
|
|
4508
|
+
if _bp_module_sp is not None and hasattr(_bp_module_sp, 'show_cif_hkl'):
|
|
4509
|
+
show_hkl_for_spacing = bool(getattr(_bp_module_sp, 'show_cif_hkl', False))
|
|
4510
|
+
except Exception:
|
|
4511
|
+
pass
|
|
4512
|
+
if not show_hkl_for_spacing:
|
|
4513
|
+
try:
|
|
4514
|
+
show_hkl_for_spacing = bool(globals().get('show_cif_hkl', False))
|
|
4515
|
+
except Exception:
|
|
4516
|
+
pass
|
|
4517
|
+
|
|
4518
|
+
stacked_data = bool(args.stack or len(y_data_list) > 1)
|
|
4499
4519
|
# Calculate base and spacing based on FIXED y-axis limits (not current)
|
|
4500
4520
|
# This prevents incremental movement when toggling
|
|
4501
|
-
if
|
|
4521
|
+
if stacked_data:
|
|
4502
4522
|
global_min = min(float(a.min()) for a in y_data_list if len(a)) if y_data_list else fixed_ylim[0]
|
|
4503
|
-
base = global_min - 0.08*
|
|
4523
|
+
base = global_min - 0.08 * fixed_yr
|
|
4504
4524
|
else:
|
|
4505
4525
|
global_min = min(float(a.min()) for a in y_data_list if len(a)) if y_data_list else 0.0
|
|
4506
|
-
base = global_min - 0.06*
|
|
4526
|
+
base = global_min - 0.06 * fixed_yr
|
|
4527
|
+
spacing = xy_cif_row_spacing_yr(
|
|
4528
|
+
fixed_yr,
|
|
4529
|
+
show_titles=show_titles,
|
|
4530
|
+
show_hkl=show_hkl_for_spacing,
|
|
4531
|
+
stacked_or_multi_y=stacked_data,
|
|
4532
|
+
)
|
|
4533
|
+
bottom_margin = xy_cif_stack_bottom_margin_yr(fixed_yr, show_titles=show_titles)
|
|
4507
4534
|
|
|
4508
4535
|
# Only adjust y-axis limits if titles are visible
|
|
4509
|
-
needed_min = base - (n_rows-1)*spacing -
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4536
|
+
needed_min = base - (n_rows - 1) * spacing - bottom_margin
|
|
4537
|
+
# One y-limit state for the whole draw: must match what we keep after drawing.
|
|
4538
|
+
# Previously we set ylim to fixed_ylim / (needed_min, fixed_ylim[1]), drew with that yr,
|
|
4539
|
+
# then replaced ylim with prev_ylim — different ymax/yr broke title–tick alignment per row.
|
|
4540
|
+
if not show_titles:
|
|
4541
|
+
ylim_draw = tuple(prev_ylim)
|
|
4542
|
+
elif needed_min >= prev_ylim[0]:
|
|
4543
|
+
ylim_draw = tuple(prev_ylim)
|
|
4513
4544
|
else:
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4545
|
+
new_ymin = min(needed_min, prev_ylim[0])
|
|
4546
|
+
ylim_draw = (new_ymin, prev_ylim[1])
|
|
4547
|
+
ax.set_ylim(ylim_draw)
|
|
4548
|
+
|
|
4518
4549
|
cur_ylim = ax.get_ylim()
|
|
4519
4550
|
yr = cur_ylim[1] - cur_ylim[0]
|
|
4520
4551
|
if yr <= 0: yr = 1.0
|
|
@@ -4543,7 +4574,8 @@ def batplot_main() -> int: # type: ignore
|
|
|
4543
4574
|
for i,(lab, fname, peaksQ, wl, qmax_sim, color) in enumerate(cif_tick_series):
|
|
4544
4575
|
if set_visible is not None and i < len(set_visible) and not set_visible[i]:
|
|
4545
4576
|
continue
|
|
4546
|
-
y_line = base - visible_idx*spacing
|
|
4577
|
+
y_line = base - visible_idx * spacing + xy_cif_stack_y_offset(fig, i)
|
|
4578
|
+
tick_h, hkl_y = xy_cif_tick_stack_layout(y_line, yr)
|
|
4547
4579
|
if use_2th:
|
|
4548
4580
|
if wl is None: wl = _ensure_wavelength_for_2theta()
|
|
4549
4581
|
domain_peaks = _Q_to_2theta(peaksQ, wl)
|
|
@@ -4559,10 +4591,11 @@ def batplot_main() -> int: # type: ignore
|
|
|
4559
4591
|
if show_titles:
|
|
4560
4592
|
# Removed numbering; keep space padding
|
|
4561
4593
|
label_text = f" {lab}"
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4594
|
+
xy_cif_add_phase_title(
|
|
4595
|
+
ax, prev_xlim[0], y_line, tick_h, label_text,
|
|
4596
|
+
max(8, int(0.55 * plt.rcParams.get('font.size', 12))), color, new_art,
|
|
4597
|
+
)
|
|
4598
|
+
visible_idx += 1
|
|
4566
4599
|
continue
|
|
4567
4600
|
# Build map for quick hkl lookup by Q (only if hkl labels are enabled)
|
|
4568
4601
|
label_map = {}
|
|
@@ -4585,7 +4618,7 @@ def batplot_main() -> int: # type: ignore
|
|
|
4585
4618
|
if effective_show_hkl:
|
|
4586
4619
|
# For 2θ axis we convert back to Q then round; otherwise Q directly
|
|
4587
4620
|
for p in domain_peaks:
|
|
4588
|
-
ln, = ax.plot([p,p],[y_line, y_line+
|
|
4621
|
+
ln, = ax.plot([p, p], [y_line, y_line + tick_h], color=color, lw=1.0, alpha=0.9, zorder=3)
|
|
4589
4622
|
new_art.append(ln)
|
|
4590
4623
|
if use_2th and wl:
|
|
4591
4624
|
theta = np.radians(p/2.0)
|
|
@@ -4595,37 +4628,25 @@ def batplot_main() -> int: # type: ignore
|
|
|
4595
4628
|
Qp_rounded = round(Qp, 6)
|
|
4596
4629
|
lbl = label_map.get(Qp_rounded)
|
|
4597
4630
|
if lbl:
|
|
4598
|
-
t_hkl = ax.text(p,
|
|
4631
|
+
t_hkl = ax.text(p, hkl_y, lbl, ha='center', va='bottom', fontsize=7, rotation=90, color=color)
|
|
4599
4632
|
new_art.append(t_hkl)
|
|
4600
4633
|
else:
|
|
4601
4634
|
# Just draw ticks (no hkl labels)
|
|
4602
4635
|
for p in domain_peaks:
|
|
4603
|
-
ln, = ax.plot([p,p],[y_line, y_line+
|
|
4636
|
+
ln, = ax.plot([p, p], [y_line, y_line + tick_h], color=color, lw=1.0, alpha=0.9, zorder=3)
|
|
4604
4637
|
new_art.append(ln)
|
|
4605
4638
|
# Removed numbering; keep space padding (placed per CIF row)
|
|
4606
4639
|
# Only add title label if show_cif_titles is True
|
|
4607
4640
|
if show_titles:
|
|
4608
4641
|
label_text = f" {lab}"
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4642
|
+
xy_cif_add_phase_title(
|
|
4643
|
+
ax, prev_xlim[0], y_line, tick_h, label_text,
|
|
4644
|
+
max(8, int(0.55 * plt.rcParams.get('font.size', 12))), color, new_art,
|
|
4645
|
+
)
|
|
4613
4646
|
visible_idx += 1
|
|
4614
4647
|
ax._cif_tick_art = new_art
|
|
4615
|
-
# Restore both x and y-axis limits to prevent movement
|
|
4616
4648
|
ax.set_xlim(prev_xlim)
|
|
4617
|
-
#
|
|
4618
|
-
if not show_titles:
|
|
4619
|
-
# Titles hidden: always restore original limits
|
|
4620
|
-
ax.set_ylim(prev_ylim)
|
|
4621
|
-
elif needed_min >= prev_ylim[0]:
|
|
4622
|
-
# Titles shown but no expansion needed: restore original limits
|
|
4623
|
-
ax.set_ylim(prev_ylim)
|
|
4624
|
-
else:
|
|
4625
|
-
# Expansion needed: use the minimum of needed_min and prev_ylim[0] to prevent incremental growth
|
|
4626
|
-
# This ensures that repeated toggles don't cause drift
|
|
4627
|
-
new_ymin = min(needed_min, prev_ylim[0])
|
|
4628
|
-
ax.set_ylim(new_ymin, prev_ylim[1])
|
|
4649
|
+
# y-axis already set to ylim_draw before draw; avoid second set_ylim (changed yr vs. tick/title geometry)
|
|
4629
4650
|
# Store simplified metadata for hover: list of dicts with 'x','y','label'
|
|
4630
4651
|
hover_meta = []
|
|
4631
4652
|
show_hkl = globals().get('show_cif_hkl', False)
|
|
@@ -4643,14 +4664,25 @@ def batplot_main() -> int: # type: ignore
|
|
|
4643
4664
|
domain_peaks = [p for p in domain_peaks if xlow <= p <= xhigh]
|
|
4644
4665
|
if not domain_peaks:
|
|
4645
4666
|
continue
|
|
4646
|
-
# y baseline for this series (same
|
|
4647
|
-
|
|
4667
|
+
# y baseline for this series (same spacing as main CIF draw)
|
|
4668
|
+
show_hkl_h = bool(globals().get('show_cif_hkl', False))
|
|
4669
|
+
try:
|
|
4670
|
+
_bm = sys.modules.get('__main__')
|
|
4671
|
+
if _bm is not None and hasattr(_bm, 'show_cif_hkl'):
|
|
4672
|
+
show_hkl_h = bool(getattr(_bm, 'show_cif_hkl', False))
|
|
4673
|
+
except Exception:
|
|
4674
|
+
pass
|
|
4675
|
+
_stacked = bool(args.stack or len(y_data_list) > 1)
|
|
4676
|
+
if _stacked:
|
|
4648
4677
|
global_min = min(float(a.min()) for a in y_data_list if len(a)) if y_data_list else ax.get_ylim()[0]
|
|
4649
|
-
base = global_min - 0.08*
|
|
4678
|
+
base = global_min - 0.08 * yr
|
|
4650
4679
|
else:
|
|
4651
4680
|
global_min = min(float(a.min()) for a in y_data_list if len(a)) if y_data_list else 0.0
|
|
4652
|
-
base = global_min - 0.06*
|
|
4653
|
-
|
|
4681
|
+
base = global_min - 0.06 * yr
|
|
4682
|
+
spacing = xy_cif_row_spacing_yr(
|
|
4683
|
+
yr, show_titles=show_titles, show_hkl=show_hkl_h, stacked_or_multi_y=_stacked
|
|
4684
|
+
)
|
|
4685
|
+
y_line = base - i * spacing + xy_cif_stack_y_offset(fig, i)
|
|
4654
4686
|
label_map = cif_hkl_label_map.get(fname, {}) if show_hkl else {}
|
|
4655
4687
|
for p in domain_peaks:
|
|
4656
4688
|
if use_2th and wl:
|
|
@@ -33,6 +33,7 @@ from .utils import (
|
|
|
33
33
|
get_organized_path,
|
|
34
34
|
natural_sort_key,
|
|
35
35
|
print_label_latex_tips,
|
|
36
|
+
normalize_xy_cif_stack_y_offsets,
|
|
36
37
|
)
|
|
37
38
|
import time
|
|
38
39
|
from .session import dump_session as _bp_dump_session
|
|
@@ -1636,6 +1637,10 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
1636
1637
|
snap["cif_set_visible"] = list(getattr(_bp_module_snap, 'cif_set_visible') or [])
|
|
1637
1638
|
except Exception:
|
|
1638
1639
|
pass
|
|
1640
|
+
try:
|
|
1641
|
+
snap["cif_stack_y_offsets"] = list(getattr(fig, '_bp_cif_stack_y_offsets', []) or [])
|
|
1642
|
+
except Exception:
|
|
1643
|
+
pass
|
|
1639
1644
|
# Line + data arrays
|
|
1640
1645
|
for i, ln in _iter_lines():
|
|
1641
1646
|
snap["lines"].append({
|
|
@@ -2039,6 +2044,11 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2039
2044
|
_bp_module.cif_set_visible = list(snap['cif_set_visible'])
|
|
2040
2045
|
except Exception:
|
|
2041
2046
|
pass
|
|
2047
|
+
if 'cif_stack_y_offsets' in snap:
|
|
2048
|
+
try:
|
|
2049
|
+
fig._bp_cif_stack_y_offsets = list(snap['cif_stack_y_offsets'])
|
|
2050
|
+
except Exception:
|
|
2051
|
+
pass
|
|
2042
2052
|
# Redraw CIF ticks after restoration if available
|
|
2043
2053
|
if hasattr(ax, '_cif_draw_func'):
|
|
2044
2054
|
try:
|
|
@@ -2131,11 +2141,12 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2131
2141
|
# Accept both 'j' (legacy) and 't' (to match operando) for title toggle
|
|
2132
2142
|
print(" " + colorize_menu(titles_desc))
|
|
2133
2143
|
print(" " + colorize_menu(order_desc))
|
|
2144
|
+
print(" " + colorize_menu("p: shift all CIF ticks (w/s or type a value)"))
|
|
2134
2145
|
print(" " + colorize_menu("c: CIF color (per set)"))
|
|
2135
2146
|
print(" " + colorize_menu("x: show/hide CIF set"))
|
|
2136
2147
|
print(" " + colorize_menu("r: rename CIF set label"))
|
|
2137
2148
|
print(" " + colorize_menu("q: back to main menu"))
|
|
2138
|
-
sub = _safe_input(colorize_prompt("CIF (c/x/r/q): ")).strip().lower()
|
|
2149
|
+
sub = _safe_input(colorize_prompt("CIF (z/t/v/p/c/x/r/q): ")).strip().lower()
|
|
2139
2150
|
if not sub or sub == 'q':
|
|
2140
2151
|
break
|
|
2141
2152
|
if sub == 'z':
|
|
@@ -2238,11 +2249,94 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
|
|
|
2238
2249
|
new_cts = [cts[i - 1] for i in parts]
|
|
2239
2250
|
if _bp is not None:
|
|
2240
2251
|
setattr(_bp, 'cif_tick_series', new_cts)
|
|
2252
|
+
prev_offs = getattr(fig, '_bp_cif_stack_y_offsets', None)
|
|
2253
|
+
if prev_offs is not None and len(prev_offs) == len(cts):
|
|
2254
|
+
fig._bp_cif_stack_y_offsets = [prev_offs[i - 1] for i in parts]
|
|
2241
2255
|
if hasattr(ax, '_cif_draw_func'):
|
|
2242
2256
|
ax._cif_draw_func()
|
|
2243
2257
|
print("Updated CIF vertical order.")
|
|
2244
2258
|
except Exception as e:
|
|
2245
2259
|
print(f"Error reordering CIF sets: {e}")
|
|
2260
|
+
elif sub == 'p':
|
|
2261
|
+
# Same offset list length as CIF sets: apply one shared data-Y shift to every row,
|
|
2262
|
+
# or w/s nudge all stacks by fixed typographic points on screen (2 pt).
|
|
2263
|
+
_CIF_NUDGE_PT = 2.0
|
|
2264
|
+
|
|
2265
|
+
def _dy_data_for_display_pts(ax_, dy_pts):
|
|
2266
|
+
try:
|
|
2267
|
+
x_ref = float(np.mean(ax_.get_xlim()))
|
|
2268
|
+
y_ref = float(np.mean(ax_.get_ylim()))
|
|
2269
|
+
p0 = np.asarray(ax_.transData.transform((x_ref, y_ref)), dtype=float)
|
|
2270
|
+
fig_ = ax_.figure
|
|
2271
|
+
d_pix = float(dy_pts) * (float(fig_.dpi) / 72.0)
|
|
2272
|
+
p1 = p0 + np.array([0.0, d_pix], dtype=float)
|
|
2273
|
+
y1 = float(ax_.transData.inverted().transform(tuple(p1))[1])
|
|
2274
|
+
return y1 - y_ref
|
|
2275
|
+
except Exception:
|
|
2276
|
+
return 0.0
|
|
2277
|
+
|
|
2278
|
+
try:
|
|
2279
|
+
cts = getattr(_bp, 'cif_tick_series', None) if _bp is not None else None
|
|
2280
|
+
if not cts:
|
|
2281
|
+
print("No CIF tick sets.")
|
|
2282
|
+
else:
|
|
2283
|
+
n = len(cts)
|
|
2284
|
+
_Hi = '\033[1m'
|
|
2285
|
+
_Cc = '\033[96m'
|
|
2286
|
+
_Rn = '\033[0m'
|
|
2287
|
+
while True:
|
|
2288
|
+
normalize_xy_cif_stack_y_offsets(fig, n)
|
|
2289
|
+
offs = list(fig._bp_cif_stack_y_offsets)
|
|
2290
|
+
print(
|
|
2291
|
+
f"\n{_Hi}All CIF ticks:{_Rn} {_Hi}{_Cc}w{_Rn}/{_Hi}{_Cc}s{_Rn} nudge all "
|
|
2292
|
+
f"(±{_CIF_NUDGE_PT:g} pt), or a {_Hi}{_Cc}number{_Rn} for all; "
|
|
2293
|
+
f"{_Hi}{_Cc}0{_Rn} clear; {_Hi}{_Cc}q{_Rn} leave."
|
|
2294
|
+
)
|
|
2295
|
+
line = _safe_input(colorize_prompt("(w/s/value/0/q): ")).strip().lower()
|
|
2296
|
+
if not line or line == 'q':
|
|
2297
|
+
break
|
|
2298
|
+
dd = _dy_data_for_display_pts(ax, _CIF_NUDGE_PT)
|
|
2299
|
+
if line == 'w':
|
|
2300
|
+
try:
|
|
2301
|
+
push_state("cif-stack-y-offset")
|
|
2302
|
+
except Exception:
|
|
2303
|
+
pass
|
|
2304
|
+
fig._bp_cif_stack_y_offsets = [float(o) + dd for o in offs]
|
|
2305
|
+
if hasattr(ax, '_cif_draw_func'):
|
|
2306
|
+
ax._cif_draw_func()
|
|
2307
|
+
continue
|
|
2308
|
+
if line == 's':
|
|
2309
|
+
try:
|
|
2310
|
+
push_state("cif-stack-y-offset")
|
|
2311
|
+
except Exception:
|
|
2312
|
+
pass
|
|
2313
|
+
fig._bp_cif_stack_y_offsets = [float(o) - dd for o in offs]
|
|
2314
|
+
if hasattr(ax, '_cif_draw_func'):
|
|
2315
|
+
ax._cif_draw_func()
|
|
2316
|
+
continue
|
|
2317
|
+
if line in ('reset', '0', 'zero'):
|
|
2318
|
+
try:
|
|
2319
|
+
push_state("cif-stack-y-offset")
|
|
2320
|
+
except Exception:
|
|
2321
|
+
pass
|
|
2322
|
+
fig._bp_cif_stack_y_offsets = [0.0] * n
|
|
2323
|
+
if hasattr(ax, '_cif_draw_func'):
|
|
2324
|
+
ax._cif_draw_func()
|
|
2325
|
+
continue
|
|
2326
|
+
try:
|
|
2327
|
+
val = float(line)
|
|
2328
|
+
except ValueError:
|
|
2329
|
+
print("w, s, a number, or q.")
|
|
2330
|
+
continue
|
|
2331
|
+
try:
|
|
2332
|
+
push_state("cif-stack-y-offset")
|
|
2333
|
+
except Exception:
|
|
2334
|
+
pass
|
|
2335
|
+
fig._bp_cif_stack_y_offsets = [float(val)] * n
|
|
2336
|
+
if hasattr(ax, '_cif_draw_func'):
|
|
2337
|
+
ax._cif_draw_func()
|
|
2338
|
+
except Exception as e:
|
|
2339
|
+
print(f"Error setting CIF offsets: {e}")
|
|
2246
2340
|
elif sub == 'c':
|
|
2247
2341
|
# CIF colors: support per-set mappings and palette-like tokens, mirroring main color menu behavior.
|
|
2248
2342
|
try:
|
|
@@ -48,7 +48,15 @@ from matplotlib.ticker import (
|
|
|
48
48
|
NullFormatter, FuncFormatter, MaxNLocator,
|
|
49
49
|
)
|
|
50
50
|
|
|
51
|
-
from .utils import
|
|
51
|
+
from .utils import (
|
|
52
|
+
_confirm_overwrite,
|
|
53
|
+
ensure_exact_case_filename,
|
|
54
|
+
xy_cif_stack_y_offset,
|
|
55
|
+
xy_cif_tick_stack_layout,
|
|
56
|
+
xy_cif_add_phase_title,
|
|
57
|
+
xy_cif_row_spacing_yr,
|
|
58
|
+
xy_cif_stack_bottom_margin_yr,
|
|
59
|
+
)
|
|
52
60
|
from .color_utils import ensure_colormap
|
|
53
61
|
from .ui import (
|
|
54
62
|
set_spine_side_color as _set_spine_side_color,
|
|
@@ -656,7 +664,18 @@ def dump_session(
|
|
|
656
664
|
'cif_hkl_label_map': {k: dict(v) for k, v in (cif_hkl_label_map or {}).items()},
|
|
657
665
|
'show_cif_hkl': bool(show_cif_hkl),
|
|
658
666
|
'show_cif_titles': bool(show_cif_titles) if show_cif_titles is not None else True,
|
|
667
|
+
'cif_stack_y_offsets': list(getattr(fig, '_bp_cif_stack_y_offsets', []) or []),
|
|
659
668
|
}
|
|
669
|
+
# 1D XY: per-set CIF visibility (__main__.cif_set_visible), same as undo snapshots
|
|
670
|
+
try:
|
|
671
|
+
if cif_tick_series:
|
|
672
|
+
_m = sys.modules.get("__main__")
|
|
673
|
+
if _m is not None and hasattr(_m, "cif_set_visible"):
|
|
674
|
+
_vis = list(getattr(_m, "cif_set_visible") or [])
|
|
675
|
+
if len(_vis) == len(list(cif_tick_series or [])):
|
|
676
|
+
sess["cif_set_visible"] = [bool(v) for v in _vis]
|
|
677
|
+
except Exception:
|
|
678
|
+
pass
|
|
660
679
|
sess['axis_titles'] = {
|
|
661
680
|
'top_x': bool(getattr(ax, '_top_xlabel_on', False)),
|
|
662
681
|
'right_y': bool(getattr(ax, '_right_ylabel_on', False)),
|
|
@@ -3969,6 +3988,32 @@ def load_xy_session(filename: str):
|
|
|
3969
3988
|
setattr(_bp_module, 'cif_extend_suspended', False)
|
|
3970
3989
|
except Exception:
|
|
3971
3990
|
pass
|
|
3991
|
+
try:
|
|
3992
|
+
co = sess.get('cif_stack_y_offsets')
|
|
3993
|
+
if co is not None and cif_tick_series:
|
|
3994
|
+
olist = []
|
|
3995
|
+
for x in list(co):
|
|
3996
|
+
try:
|
|
3997
|
+
olist.append(float(x))
|
|
3998
|
+
except (TypeError, ValueError):
|
|
3999
|
+
olist.append(0.0)
|
|
4000
|
+
while len(olist) < len(cif_tick_series):
|
|
4001
|
+
olist.append(0.0)
|
|
4002
|
+
fig._bp_cif_stack_y_offsets = olist[: len(cif_tick_series)]
|
|
4003
|
+
except Exception:
|
|
4004
|
+
pass
|
|
4005
|
+
try:
|
|
4006
|
+
vis = sess.get("cif_set_visible")
|
|
4007
|
+
if vis is not None and cif_tick_series:
|
|
4008
|
+
vlist = [bool(v) for v in list(vis)]
|
|
4009
|
+
while len(vlist) < len(cif_tick_series):
|
|
4010
|
+
vlist.append(True)
|
|
4011
|
+
vlist = vlist[: len(cif_tick_series)]
|
|
4012
|
+
_m = sys.modules.get("__main__")
|
|
4013
|
+
if _m is not None:
|
|
4014
|
+
setattr(_m, "cif_set_visible", vlist)
|
|
4015
|
+
except Exception:
|
|
4016
|
+
pass
|
|
3972
4017
|
|
|
3973
4018
|
axis_mode_restored = sess.get('axis_mode', 'unknown')
|
|
3974
4019
|
use_Q = axis_mode_restored == 'Q'
|
|
@@ -4050,19 +4095,29 @@ def load_xy_session(filename: str):
|
|
|
4050
4095
|
pass
|
|
4051
4096
|
if not show_hkl_local:
|
|
4052
4097
|
show_hkl_local = bool(show_cif_hkl)
|
|
4053
|
-
|
|
4098
|
+
_stacked_xy = bool(saved_stack or len(y_data_list) > 1)
|
|
4099
|
+
if _stacked_xy:
|
|
4054
4100
|
global_min = min(float(a.min()) for a in y_data_list if len(a)) if y_data_list else fixed_ylim[0]
|
|
4055
4101
|
base = global_min - 0.08 * fixed_yr
|
|
4056
|
-
spacing = 0.05 * fixed_yr
|
|
4057
4102
|
else:
|
|
4058
4103
|
global_min = min(float(a.min()) for a in y_data_list if len(a)) if y_data_list else 0.0
|
|
4059
4104
|
base = global_min - 0.06 * fixed_yr
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4105
|
+
spacing = xy_cif_row_spacing_yr(
|
|
4106
|
+
fixed_yr,
|
|
4107
|
+
show_titles=show_titles_local,
|
|
4108
|
+
show_hkl=show_hkl_local,
|
|
4109
|
+
stacked_or_multi_y=_stacked_xy,
|
|
4110
|
+
)
|
|
4111
|
+
_cif_bottom_m = xy_cif_stack_bottom_margin_yr(fixed_yr, show_titles=show_titles_local)
|
|
4112
|
+
needed_min = base - (len(cif_tick_series) - 1) * spacing - _cif_bottom_m
|
|
4113
|
+
if not show_titles_local:
|
|
4114
|
+
ylim_draw = tuple(prev_ylim)
|
|
4115
|
+
elif needed_min >= prev_ylim[0]:
|
|
4116
|
+
ylim_draw = tuple(prev_ylim)
|
|
4064
4117
|
else:
|
|
4065
|
-
|
|
4118
|
+
new_ymin = min(needed_min, prev_ylim[0])
|
|
4119
|
+
ylim_draw = (new_ymin, prev_ylim[1])
|
|
4120
|
+
ax.set_ylim(ylim_draw)
|
|
4066
4121
|
cur_ylim = ax.get_ylim()
|
|
4067
4122
|
yr = cur_ylim[1] - cur_ylim[0]
|
|
4068
4123
|
if yr <= 0:
|
|
@@ -4075,7 +4130,8 @@ def load_xy_session(filename: str):
|
|
|
4075
4130
|
new_art = []
|
|
4076
4131
|
wl_any = _session_ensure_wavelength()
|
|
4077
4132
|
for i, (lab, fname, peaksQ, wl, qmax_sim, color) in enumerate(cif_tick_series):
|
|
4078
|
-
y_line = base - i * spacing
|
|
4133
|
+
y_line = base - i * spacing + xy_cif_stack_y_offset(fig, i)
|
|
4134
|
+
tick_h, hkl_y = xy_cif_tick_stack_layout(y_line, yr)
|
|
4079
4135
|
if use_2th:
|
|
4080
4136
|
wl_use = wl if wl is not None else wl_any
|
|
4081
4137
|
domain_peaks = _session_q_to_2theta(peaksQ, wl_use)
|
|
@@ -4090,7 +4146,7 @@ def load_xy_session(filename: str):
|
|
|
4090
4146
|
show_hkl_local = False
|
|
4091
4147
|
label_map = {}
|
|
4092
4148
|
for p in domain_peaks:
|
|
4093
|
-
ln, = ax.plot([p, p], [y_line, y_line +
|
|
4149
|
+
ln, = ax.plot([p, p], [y_line, y_line + tick_h], color=color, lw=1.0, alpha=0.9, zorder=3)
|
|
4094
4150
|
new_art.append(ln)
|
|
4095
4151
|
if show_hkl_local:
|
|
4096
4152
|
if use_2th and (wl or wl_any):
|
|
@@ -4101,24 +4157,17 @@ def load_xy_session(filename: str):
|
|
|
4101
4157
|
Qp_rounded = round(Qp, 6)
|
|
4102
4158
|
lbl = label_map.get(Qp_rounded)
|
|
4103
4159
|
if lbl:
|
|
4104
|
-
t_hkl = ax.text(p,
|
|
4160
|
+
t_hkl = ax.text(p, hkl_y, lbl, ha='center', va='bottom',
|
|
4105
4161
|
fontsize=7, rotation=90, color=color)
|
|
4106
4162
|
new_art.append(t_hkl)
|
|
4107
4163
|
if show_titles_local:
|
|
4108
4164
|
label_text = f" {lab}"
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4165
|
+
xy_cif_add_phase_title(
|
|
4166
|
+
ax, prev_xlim[0], y_line, tick_h, label_text,
|
|
4167
|
+
max(8, int(0.55 * plt.rcParams.get('font.size', 16))), color, new_art,
|
|
4168
|
+
)
|
|
4113
4169
|
ax._cif_tick_art = new_art
|
|
4114
4170
|
ax.set_xlim(prev_xlim)
|
|
4115
|
-
if not show_titles_local:
|
|
4116
|
-
ax.set_ylim(prev_ylim)
|
|
4117
|
-
elif needed_min >= prev_ylim[0]:
|
|
4118
|
-
ax.set_ylim(prev_ylim)
|
|
4119
|
-
else:
|
|
4120
|
-
new_ymin = min(needed_min, prev_ylim[0])
|
|
4121
|
-
ax.set_ylim(new_ymin, prev_ylim[1])
|
|
4122
4171
|
fig.canvas.draw_idle()
|
|
4123
4172
|
except Exception:
|
|
4124
4173
|
pass
|
|
@@ -530,19 +530,37 @@ def print_style_info(
|
|
|
530
530
|
print(f"Label anchor: {legend_pos} (stack={getattr(args, 'stack', False)})")
|
|
531
531
|
print(f"Labels (r): x='{ax.get_xlabel() or ''}', y='{ax.get_ylabel() or ''}'")
|
|
532
532
|
|
|
533
|
-
# ---- CIF (z,
|
|
534
|
-
if
|
|
535
|
-
print(f"\n--- CIF (
|
|
536
|
-
print(f"CIF hkl labels (z): {'shown' if show_cif_hkl else 'hidden'}")
|
|
537
|
-
elif cif_tick_series:
|
|
533
|
+
# ---- CIF (cif submenu: z, t, v, p, …) ---
|
|
534
|
+
if cif_tick_series:
|
|
535
|
+
print(f"\n--- CIF (cif key) ---")
|
|
538
536
|
try:
|
|
537
|
+
hkl_state = None
|
|
539
538
|
_bp_module = sys.modules.get('__main__')
|
|
540
539
|
if _bp_module is not None and hasattr(_bp_module, 'show_cif_hkl'):
|
|
541
540
|
hkl_state = bool(getattr(_bp_module, 'show_cif_hkl', False))
|
|
542
|
-
|
|
541
|
+
elif show_cif_hkl is not None:
|
|
542
|
+
hkl_state = bool(show_cif_hkl)
|
|
543
|
+
if hkl_state is not None:
|
|
543
544
|
print(f"CIF hkl labels (z): {'shown' if hkl_state else 'hidden'}")
|
|
545
|
+
off_list = getattr(fig, '_bp_cif_stack_y_offsets', None) or []
|
|
546
|
+
if off_list:
|
|
547
|
+
bits = []
|
|
548
|
+
for i, ent in enumerate(cif_tick_series):
|
|
549
|
+
lab = ent[0] if ent else f"set{i+1}"
|
|
550
|
+
ov = float(off_list[i]) if i < len(off_list) else 0.0
|
|
551
|
+
if abs(ov) > 1e-12:
|
|
552
|
+
bits.append(f"{lab}={ov:.4g}")
|
|
553
|
+
if bits:
|
|
554
|
+
print("CIF stack Y offsets (p): " + "; ".join(bits))
|
|
555
|
+
else:
|
|
556
|
+
print("CIF stack Y offsets (p): all 0")
|
|
557
|
+
else:
|
|
558
|
+
print("CIF stack Y offsets (p): default (0 per set)")
|
|
544
559
|
except Exception:
|
|
545
560
|
pass
|
|
561
|
+
elif show_cif_hkl is not None:
|
|
562
|
+
print(f"\n--- CIF ---")
|
|
563
|
+
print(f"CIF hkl labels (z): {'shown' if show_cif_hkl else 'hidden'}")
|
|
546
564
|
|
|
547
565
|
# ---- Curves (c, o) ----
|
|
548
566
|
print("\n--- Curves (c, o) ---")
|
|
@@ -772,6 +790,17 @@ def export_style_config(
|
|
|
772
790
|
{"index": i, "color": color}
|
|
773
791
|
for i, (lab, fname, peaksQ, wl, qmax_sim, color) in enumerate(cif_tick_series)
|
|
774
792
|
]
|
|
793
|
+
# Always save one offset per CIF set so import (i) matches session/undo (s/b).
|
|
794
|
+
raw_off = getattr(fig, "_bp_cif_stack_y_offsets", None) or []
|
|
795
|
+
o2: List[float] = []
|
|
796
|
+
for x in list(raw_off)[: len(cif_tick_series)]:
|
|
797
|
+
try:
|
|
798
|
+
o2.append(float(x))
|
|
799
|
+
except (TypeError, ValueError):
|
|
800
|
+
o2.append(0.0)
|
|
801
|
+
while len(o2) < len(cif_tick_series):
|
|
802
|
+
o2.append(0.0)
|
|
803
|
+
cfg["cif_stack_y_offsets"] = o2
|
|
775
804
|
palette_history = getattr(fig, '_curve_palette_history', None)
|
|
776
805
|
if palette_history:
|
|
777
806
|
serialized_palettes = []
|
|
@@ -1402,6 +1431,20 @@ def apply_style_config(
|
|
|
1402
1431
|
lab_new = entry.get("label", lab)
|
|
1403
1432
|
color_new = entry.get("color", color_old)
|
|
1404
1433
|
cif_tick_series[idx] = (lab_new, fname, peaksQ, wl, qmax_sim, color_new)
|
|
1434
|
+
if "cif_stack_y_offsets" in cfg and cif_tick_series is not None:
|
|
1435
|
+
try:
|
|
1436
|
+
raw_o = cfg.get("cif_stack_y_offsets") or []
|
|
1437
|
+
o2 = []
|
|
1438
|
+
for x in list(raw_o)[: len(cif_tick_series)]:
|
|
1439
|
+
try:
|
|
1440
|
+
o2.append(float(x))
|
|
1441
|
+
except (TypeError, ValueError):
|
|
1442
|
+
o2.append(0.0)
|
|
1443
|
+
while len(o2) < len(cif_tick_series):
|
|
1444
|
+
o2.append(0.0)
|
|
1445
|
+
fig._bp_cif_stack_y_offsets = o2
|
|
1446
|
+
except Exception:
|
|
1447
|
+
pass
|
|
1405
1448
|
# Restore CIF title visibility
|
|
1406
1449
|
if "show_cif_titles" in cfg:
|
|
1407
1450
|
try:
|
|
@@ -1463,7 +1506,8 @@ def apply_style_config(
|
|
|
1463
1506
|
# as style files are for styling only, and the data would be specific
|
|
1464
1507
|
# to the dataset. Session files (pickle) store this data instead.
|
|
1465
1508
|
# Redraw CIF ticks after applying changes
|
|
1466
|
-
if (cif_cfg and cif_tick_series is not None) or "show_cif_titles" in cfg or "show_cif_hkl" in cfg
|
|
1509
|
+
if ((cif_cfg and cif_tick_series is not None) or "show_cif_titles" in cfg or "show_cif_hkl" in cfg
|
|
1510
|
+
or "cif_stack_y_offsets" in cfg):
|
|
1467
1511
|
if hasattr(ax, "_cif_draw_func"):
|
|
1468
1512
|
try:
|
|
1469
1513
|
ax._cif_draw_func()
|
|
@@ -41,6 +41,8 @@ import subprocess
|
|
|
41
41
|
import time
|
|
42
42
|
from typing import Callable, List, Optional, Tuple
|
|
43
43
|
|
|
44
|
+
from matplotlib.transforms import offset_copy
|
|
45
|
+
|
|
44
46
|
|
|
45
47
|
def natural_sort_key(name: str) -> list:
|
|
46
48
|
"""Generate a natural sorting key for filenames with numbers.
|
|
@@ -1139,3 +1141,122 @@ def choose_style_file(file_paths: List[str], purpose: str = "style import", exte
|
|
|
1139
1141
|
if path:
|
|
1140
1142
|
return path
|
|
1141
1143
|
print("File not found. Enter another value or use 'c' for custom dialog.")
|
|
1144
|
+
|
|
1145
|
+
|
|
1146
|
+
def xy_cif_stack_y_offset(fig, index: int) -> float:
|
|
1147
|
+
"""Vertical offset in data Y for CIF stack row ``index`` (XY / 1D mode)."""
|
|
1148
|
+
offs = getattr(fig, "_bp_cif_stack_y_offsets", None)
|
|
1149
|
+
if not offs or index < 0 or index >= len(offs):
|
|
1150
|
+
return 0.0
|
|
1151
|
+
try:
|
|
1152
|
+
return float(offs[index])
|
|
1153
|
+
except (TypeError, ValueError):
|
|
1154
|
+
return 0.0
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
# Typographic gap (screen points) from tick top to phase filename; same for every row / file.
|
|
1158
|
+
XY_CIF_TITLE_ABOVE_TICK_PT = 2.0
|
|
1159
|
+
|
|
1160
|
+
|
|
1161
|
+
def xy_cif_add_phase_title(
|
|
1162
|
+
ax,
|
|
1163
|
+
x_left: float,
|
|
1164
|
+
y_line: float,
|
|
1165
|
+
tick_h: float,
|
|
1166
|
+
label_text: str,
|
|
1167
|
+
fontsize,
|
|
1168
|
+
color,
|
|
1169
|
+
new_art: list,
|
|
1170
|
+
) -> None:
|
|
1171
|
+
"""Draw phase filename a fixed number of points above tick tops (uniform visual gap).
|
|
1172
|
+
|
|
1173
|
+
Anchor in data space at the tick tops ``(x_left, y_line + tick_h)``, then shift in
|
|
1174
|
+
**display points** via ``offset_copy`` (matplotlib's stable pattern for data+pt mix).
|
|
1175
|
+
"""
|
|
1176
|
+
fig = ax.figure
|
|
1177
|
+
trans = offset_copy(
|
|
1178
|
+
ax.transData,
|
|
1179
|
+
fig=fig,
|
|
1180
|
+
x=0.0,
|
|
1181
|
+
y=XY_CIF_TITLE_ABOVE_TICK_PT,
|
|
1182
|
+
units="points",
|
|
1183
|
+
)
|
|
1184
|
+
txt = ax.text(
|
|
1185
|
+
float(x_left),
|
|
1186
|
+
float(y_line + tick_h),
|
|
1187
|
+
label_text,
|
|
1188
|
+
transform=trans,
|
|
1189
|
+
ha="left",
|
|
1190
|
+
va="bottom",
|
|
1191
|
+
fontsize=fontsize,
|
|
1192
|
+
color=color,
|
|
1193
|
+
clip_on=False,
|
|
1194
|
+
zorder=4,
|
|
1195
|
+
)
|
|
1196
|
+
new_art.append(txt)
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
def xy_cif_tick_stack_layout(y_line: float, yr: float):
|
|
1200
|
+
"""Return (tick_h, hkl_text_y) for CIF tick geometry in data coordinates.
|
|
1201
|
+
|
|
1202
|
+
Phase titles use :func:`xy_cif_add_phase_title` (points above ``y_line + tick_h``).
|
|
1203
|
+
"""
|
|
1204
|
+
yr = max(float(yr), 1e-12)
|
|
1205
|
+
tick_h = 0.02 * yr
|
|
1206
|
+
hkl_y = y_line + tick_h + 0.005 * yr
|
|
1207
|
+
return tick_h, hkl_y
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
def xy_cif_row_spacing_yr(
|
|
1211
|
+
yr_ref: float,
|
|
1212
|
+
*,
|
|
1213
|
+
show_titles: bool,
|
|
1214
|
+
show_hkl: bool,
|
|
1215
|
+
stacked_or_multi_y: bool,
|
|
1216
|
+
) -> float:
|
|
1217
|
+
"""Vertical gap between consecutive CIF row baselines (``y_line``), in data Y units.
|
|
1218
|
+
|
|
1219
|
+
Must be large enough that phase titles (above tick stems) and optional rotated
|
|
1220
|
+
hkl labels do not collide with the next row.
|
|
1221
|
+
"""
|
|
1222
|
+
yr = max(float(yr_ref), 1e-12)
|
|
1223
|
+
if stacked_or_multi_y:
|
|
1224
|
+
spacing = 0.05 * yr
|
|
1225
|
+
else:
|
|
1226
|
+
spacing = 0.04 * yr
|
|
1227
|
+
if show_titles and show_hkl:
|
|
1228
|
+
spacing = max(spacing, 0.088 * yr)
|
|
1229
|
+
elif show_titles:
|
|
1230
|
+
spacing = max(spacing, 0.076 * yr)
|
|
1231
|
+
elif show_hkl:
|
|
1232
|
+
spacing = max(spacing, 0.056 * yr)
|
|
1233
|
+
return float(spacing)
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
def xy_cif_stack_bottom_margin_yr(yr_ref: float, *, show_titles: bool) -> float:
|
|
1237
|
+
"""Extra room below the lowest CIF row (fraction of ``yr_ref``) for axis padding."""
|
|
1238
|
+
yr = max(float(yr_ref), 1e-12)
|
|
1239
|
+
if show_titles:
|
|
1240
|
+
return float(0.055 * yr)
|
|
1241
|
+
return float(0.04 * yr)
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
def normalize_xy_cif_stack_y_offsets(fig, n_sets: int) -> list:
|
|
1245
|
+
"""Ensure ``fig._bp_cif_stack_y_offsets`` exists and has length ``n_sets``."""
|
|
1246
|
+
if n_sets <= 0:
|
|
1247
|
+
fig._bp_cif_stack_y_offsets = []
|
|
1248
|
+
return []
|
|
1249
|
+
cur = getattr(fig, "_bp_cif_stack_y_offsets", None)
|
|
1250
|
+
if cur is None:
|
|
1251
|
+
out = [0.0] * n_sets
|
|
1252
|
+
else:
|
|
1253
|
+
out = []
|
|
1254
|
+
for x in list(cur)[:n_sets]:
|
|
1255
|
+
try:
|
|
1256
|
+
out.append(float(x))
|
|
1257
|
+
except (TypeError, ValueError):
|
|
1258
|
+
out.append(0.0)
|
|
1259
|
+
if len(out) < n_sets:
|
|
1260
|
+
out.extend([0.0] * (n_sets - len(out)))
|
|
1261
|
+
fig._bp_cif_stack_y_offsets = out
|
|
1262
|
+
return out
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: batplot
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.37
|
|
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.37"
|
|
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
|
|
File without changes
|