batplot 1.8.34__tar.gz → 1.8.36__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.34/batplot.egg-info → batplot-1.8.36}/PKG-INFO +1 -1
- {batplot-1.8.34 → batplot-1.8.36}/batplot/__init__.py +1 -1
- {batplot-1.8.34 → batplot-1.8.36}/batplot/batplot.py +106 -79
- {batplot-1.8.34 → batplot-1.8.36}/batplot/data/CHANGELOG.md +8 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/electrochem_interactive.py +75 -21
- {batplot-1.8.34 → batplot-1.8.36}/batplot/interactive.py +95 -1
- {batplot-1.8.34 → batplot-1.8.36}/batplot/session.py +71 -22
- {batplot-1.8.34 → batplot-1.8.36}/batplot/style.py +51 -7
- {batplot-1.8.34 → batplot-1.8.36}/batplot/utils.py +121 -0
- {batplot-1.8.34 → batplot-1.8.36/batplot.egg-info}/PKG-INFO +1 -1
- {batplot-1.8.34 → batplot-1.8.36}/pyproject.toml +1 -1
- {batplot-1.8.34 → batplot-1.8.36}/LICENSE +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/MANIFEST.in +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/NOTICE +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/README.md +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/args.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/batch.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/canvas_interactive.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/cif.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/cli.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/color_utils.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/config.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/converters.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/cpc_interactive.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/data/USER_MANUAL.md +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/dev_upgrade.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/manual.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/modes.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/operando.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/operando_ec_interactive.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/plotting.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/readers.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/showcol.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/ui.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot/version_check.py +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot.egg-info/SOURCES.txt +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot.egg-info/dependency_links.txt +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot.egg-info/entry_points.txt +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot.egg-info/requires.txt +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/batplot.egg-info/top_level.txt +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/setup.cfg +0 -0
- {batplot-1.8.34 → batplot-1.8.36}/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.36
|
|
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
|
|
@@ -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
|
|
@@ -4496,25 +4497,50 @@ def batplot_main() -> int: # type: ignore
|
|
|
4496
4497
|
else:
|
|
4497
4498
|
n_rows = max(1, sum(1 for v in set_visible if v))
|
|
4498
4499
|
|
|
4500
|
+
show_hkl_for_spacing = False
|
|
4501
|
+
try:
|
|
4502
|
+
_bp_module_sp = sys.modules.get('__main__')
|
|
4503
|
+
if _bp_module_sp is not None and hasattr(_bp_module_sp, 'show_cif_hkl'):
|
|
4504
|
+
show_hkl_for_spacing = bool(getattr(_bp_module_sp, 'show_cif_hkl', False))
|
|
4505
|
+
except Exception:
|
|
4506
|
+
pass
|
|
4507
|
+
if not show_hkl_for_spacing:
|
|
4508
|
+
try:
|
|
4509
|
+
show_hkl_for_spacing = bool(globals().get('show_cif_hkl', False))
|
|
4510
|
+
except Exception:
|
|
4511
|
+
pass
|
|
4512
|
+
|
|
4513
|
+
stacked_data = bool(args.stack or len(y_data_list) > 1)
|
|
4499
4514
|
# Calculate base and spacing based on FIXED y-axis limits (not current)
|
|
4500
4515
|
# This prevents incremental movement when toggling
|
|
4501
|
-
if
|
|
4516
|
+
if stacked_data:
|
|
4502
4517
|
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*
|
|
4518
|
+
base = global_min - 0.08 * fixed_yr
|
|
4504
4519
|
else:
|
|
4505
4520
|
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*
|
|
4521
|
+
base = global_min - 0.06 * fixed_yr
|
|
4522
|
+
spacing = xy_cif_row_spacing_yr(
|
|
4523
|
+
fixed_yr,
|
|
4524
|
+
show_titles=show_titles,
|
|
4525
|
+
show_hkl=show_hkl_for_spacing,
|
|
4526
|
+
stacked_or_multi_y=stacked_data,
|
|
4527
|
+
)
|
|
4528
|
+
bottom_margin = xy_cif_stack_bottom_margin_yr(fixed_yr, show_titles=show_titles)
|
|
4507
4529
|
|
|
4508
4530
|
# Only adjust y-axis limits if titles are visible
|
|
4509
|
-
needed_min = base - (n_rows-1)*spacing -
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4531
|
+
needed_min = base - (n_rows - 1) * spacing - bottom_margin
|
|
4532
|
+
# One y-limit state for the whole draw: must match what we keep after drawing.
|
|
4533
|
+
# Previously we set ylim to fixed_ylim / (needed_min, fixed_ylim[1]), drew with that yr,
|
|
4534
|
+
# then replaced ylim with prev_ylim — different ymax/yr broke title–tick alignment per row.
|
|
4535
|
+
if not show_titles:
|
|
4536
|
+
ylim_draw = tuple(prev_ylim)
|
|
4537
|
+
elif needed_min >= prev_ylim[0]:
|
|
4538
|
+
ylim_draw = tuple(prev_ylim)
|
|
4513
4539
|
else:
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4540
|
+
new_ymin = min(needed_min, prev_ylim[0])
|
|
4541
|
+
ylim_draw = (new_ymin, prev_ylim[1])
|
|
4542
|
+
ax.set_ylim(ylim_draw)
|
|
4543
|
+
|
|
4518
4544
|
cur_ylim = ax.get_ylim()
|
|
4519
4545
|
yr = cur_ylim[1] - cur_ylim[0]
|
|
4520
4546
|
if yr <= 0: yr = 1.0
|
|
@@ -4543,7 +4569,8 @@ def batplot_main() -> int: # type: ignore
|
|
|
4543
4569
|
for i,(lab, fname, peaksQ, wl, qmax_sim, color) in enumerate(cif_tick_series):
|
|
4544
4570
|
if set_visible is not None and i < len(set_visible) and not set_visible[i]:
|
|
4545
4571
|
continue
|
|
4546
|
-
y_line = base - visible_idx*spacing
|
|
4572
|
+
y_line = base - visible_idx * spacing + xy_cif_stack_y_offset(fig, i)
|
|
4573
|
+
tick_h, hkl_y = xy_cif_tick_stack_layout(y_line, yr)
|
|
4547
4574
|
if use_2th:
|
|
4548
4575
|
if wl is None: wl = _ensure_wavelength_for_2theta()
|
|
4549
4576
|
domain_peaks = _Q_to_2theta(peaksQ, wl)
|
|
@@ -4559,10 +4586,11 @@ def batplot_main() -> int: # type: ignore
|
|
|
4559
4586
|
if show_titles:
|
|
4560
4587
|
# Removed numbering; keep space padding
|
|
4561
4588
|
label_text = f" {lab}"
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4589
|
+
xy_cif_add_phase_title(
|
|
4590
|
+
ax, prev_xlim[0], y_line, tick_h, label_text,
|
|
4591
|
+
max(8, int(0.55 * plt.rcParams.get('font.size', 12))), color, new_art,
|
|
4592
|
+
)
|
|
4593
|
+
visible_idx += 1
|
|
4566
4594
|
continue
|
|
4567
4595
|
# Build map for quick hkl lookup by Q (only if hkl labels are enabled)
|
|
4568
4596
|
label_map = {}
|
|
@@ -4585,7 +4613,7 @@ def batplot_main() -> int: # type: ignore
|
|
|
4585
4613
|
if effective_show_hkl:
|
|
4586
4614
|
# For 2θ axis we convert back to Q then round; otherwise Q directly
|
|
4587
4615
|
for p in domain_peaks:
|
|
4588
|
-
ln, = ax.plot([p,p],[y_line, y_line+
|
|
4616
|
+
ln, = ax.plot([p, p], [y_line, y_line + tick_h], color=color, lw=1.0, alpha=0.9, zorder=3)
|
|
4589
4617
|
new_art.append(ln)
|
|
4590
4618
|
if use_2th and wl:
|
|
4591
4619
|
theta = np.radians(p/2.0)
|
|
@@ -4595,37 +4623,25 @@ def batplot_main() -> int: # type: ignore
|
|
|
4595
4623
|
Qp_rounded = round(Qp, 6)
|
|
4596
4624
|
lbl = label_map.get(Qp_rounded)
|
|
4597
4625
|
if lbl:
|
|
4598
|
-
t_hkl = ax.text(p,
|
|
4626
|
+
t_hkl = ax.text(p, hkl_y, lbl, ha='center', va='bottom', fontsize=7, rotation=90, color=color)
|
|
4599
4627
|
new_art.append(t_hkl)
|
|
4600
4628
|
else:
|
|
4601
4629
|
# Just draw ticks (no hkl labels)
|
|
4602
4630
|
for p in domain_peaks:
|
|
4603
|
-
ln, = ax.plot([p,p],[y_line, y_line+
|
|
4631
|
+
ln, = ax.plot([p, p], [y_line, y_line + tick_h], color=color, lw=1.0, alpha=0.9, zorder=3)
|
|
4604
4632
|
new_art.append(ln)
|
|
4605
4633
|
# Removed numbering; keep space padding (placed per CIF row)
|
|
4606
4634
|
# Only add title label if show_cif_titles is True
|
|
4607
4635
|
if show_titles:
|
|
4608
4636
|
label_text = f" {lab}"
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4637
|
+
xy_cif_add_phase_title(
|
|
4638
|
+
ax, prev_xlim[0], y_line, tick_h, label_text,
|
|
4639
|
+
max(8, int(0.55 * plt.rcParams.get('font.size', 12))), color, new_art,
|
|
4640
|
+
)
|
|
4613
4641
|
visible_idx += 1
|
|
4614
4642
|
ax._cif_tick_art = new_art
|
|
4615
|
-
# Restore both x and y-axis limits to prevent movement
|
|
4616
4643
|
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])
|
|
4644
|
+
# y-axis already set to ylim_draw before draw; avoid second set_ylim (changed yr vs. tick/title geometry)
|
|
4629
4645
|
# Store simplified metadata for hover: list of dicts with 'x','y','label'
|
|
4630
4646
|
hover_meta = []
|
|
4631
4647
|
show_hkl = globals().get('show_cif_hkl', False)
|
|
@@ -4643,14 +4659,25 @@ def batplot_main() -> int: # type: ignore
|
|
|
4643
4659
|
domain_peaks = [p for p in domain_peaks if xlow <= p <= xhigh]
|
|
4644
4660
|
if not domain_peaks:
|
|
4645
4661
|
continue
|
|
4646
|
-
# y baseline for this series (same
|
|
4647
|
-
|
|
4662
|
+
# y baseline for this series (same spacing as main CIF draw)
|
|
4663
|
+
show_hkl_h = bool(globals().get('show_cif_hkl', False))
|
|
4664
|
+
try:
|
|
4665
|
+
_bm = sys.modules.get('__main__')
|
|
4666
|
+
if _bm is not None and hasattr(_bm, 'show_cif_hkl'):
|
|
4667
|
+
show_hkl_h = bool(getattr(_bm, 'show_cif_hkl', False))
|
|
4668
|
+
except Exception:
|
|
4669
|
+
pass
|
|
4670
|
+
_stacked = bool(args.stack or len(y_data_list) > 1)
|
|
4671
|
+
if _stacked:
|
|
4648
4672
|
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*
|
|
4673
|
+
base = global_min - 0.08 * yr
|
|
4650
4674
|
else:
|
|
4651
4675
|
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
|
-
|
|
4676
|
+
base = global_min - 0.06 * yr
|
|
4677
|
+
spacing = xy_cif_row_spacing_yr(
|
|
4678
|
+
yr, show_titles=show_titles, show_hkl=show_hkl_h, stacked_or_multi_y=_stacked
|
|
4679
|
+
)
|
|
4680
|
+
y_line = base - i * spacing + xy_cif_stack_y_offset(fig, i)
|
|
4654
4681
|
label_map = cif_hkl_label_map.get(fname, {}) if show_hkl else {}
|
|
4655
4682
|
for p in domain_peaks:
|
|
4656
4683
|
if use_2th and wl:
|
|
@@ -281,12 +281,24 @@ def _apply_stored_smooth_settings(cycle_lines: Dict[int, Dict[str, Optional[Any]
|
|
|
281
281
|
continue
|
|
282
282
|
xdata = np.asarray(ln.get_xdata(), float)
|
|
283
283
|
ydata = np.asarray(ln.get_ydata(), float)
|
|
284
|
+
if xdata.size != ydata.size:
|
|
285
|
+
n = int(min(xdata.size, ydata.size))
|
|
286
|
+
if n < 3:
|
|
287
|
+
continue
|
|
288
|
+
xdata = xdata[:n]
|
|
289
|
+
ydata = ydata[:n]
|
|
284
290
|
if xdata.size < 3:
|
|
285
291
|
continue
|
|
286
292
|
# Get original data if available, otherwise use current data
|
|
287
293
|
if hasattr(ln, '_original_xdata'):
|
|
288
294
|
xdata = np.asarray(ln._original_xdata, float)
|
|
289
295
|
ydata = np.asarray(ln._original_ydata, float)
|
|
296
|
+
if xdata.size != ydata.size:
|
|
297
|
+
n = int(min(xdata.size, ydata.size))
|
|
298
|
+
if n < 3:
|
|
299
|
+
continue
|
|
300
|
+
xdata = xdata[:n]
|
|
301
|
+
ydata = ydata[:n]
|
|
290
302
|
else:
|
|
291
303
|
ln._original_xdata = np.array(xdata, copy=True)
|
|
292
304
|
ln._original_ydata = np.array(ydata, copy=True)
|
|
@@ -309,12 +321,24 @@ def _apply_stored_smooth_settings(cycle_lines: Dict[int, Dict[str, Optional[Any]
|
|
|
309
321
|
continue
|
|
310
322
|
xdata = np.asarray(ln.get_xdata(), float)
|
|
311
323
|
ydata = np.asarray(ln.get_ydata(), float)
|
|
324
|
+
if xdata.size != ydata.size:
|
|
325
|
+
n = int(min(xdata.size, ydata.size))
|
|
326
|
+
if n < 3:
|
|
327
|
+
continue
|
|
328
|
+
xdata = xdata[:n]
|
|
329
|
+
ydata = ydata[:n]
|
|
312
330
|
if xdata.size < 3:
|
|
313
331
|
continue
|
|
314
332
|
# Get original data if available, otherwise use current data
|
|
315
333
|
if hasattr(ln, '_original_xdata'):
|
|
316
334
|
xdata = np.asarray(ln._original_xdata, float)
|
|
317
335
|
ydata = np.asarray(ln._original_ydata, float)
|
|
336
|
+
if xdata.size != ydata.size:
|
|
337
|
+
n = int(min(xdata.size, ydata.size))
|
|
338
|
+
if n < 3:
|
|
339
|
+
continue
|
|
340
|
+
xdata = xdata[:n]
|
|
341
|
+
ydata = ydata[:n]
|
|
318
342
|
else:
|
|
319
343
|
ln._original_xdata = np.array(xdata, copy=True)
|
|
320
344
|
ln._original_ydata = np.array(ydata, copy=True)
|
|
@@ -341,12 +365,24 @@ def _apply_stored_smooth_settings(cycle_lines: Dict[int, Dict[str, Optional[Any]
|
|
|
341
365
|
continue
|
|
342
366
|
xdata = np.asarray(ln.get_xdata(), float)
|
|
343
367
|
ydata = np.asarray(ln.get_ydata(), float)
|
|
368
|
+
if xdata.size != ydata.size:
|
|
369
|
+
n = int(min(xdata.size, ydata.size))
|
|
370
|
+
if n < 5:
|
|
371
|
+
continue
|
|
372
|
+
xdata = xdata[:n]
|
|
373
|
+
ydata = ydata[:n]
|
|
344
374
|
if xdata.size < 5:
|
|
345
375
|
continue
|
|
346
376
|
# Get original data if available, otherwise use current data
|
|
347
377
|
if hasattr(ln, '_original_xdata'):
|
|
348
378
|
xdata = np.asarray(ln._original_xdata, float)
|
|
349
379
|
ydata = np.asarray(ln._original_ydata, float)
|
|
380
|
+
if xdata.size != ydata.size:
|
|
381
|
+
n = int(min(xdata.size, ydata.size))
|
|
382
|
+
if n < 5:
|
|
383
|
+
continue
|
|
384
|
+
xdata = xdata[:n]
|
|
385
|
+
ydata = ydata[:n]
|
|
350
386
|
else:
|
|
351
387
|
ln._original_xdata = np.array(xdata, copy=True)
|
|
352
388
|
ln._original_ydata = np.array(ydata, copy=True)
|
|
@@ -5741,27 +5777,33 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Optional[Dict[int, Dict[s
|
|
|
5741
5777
|
if ln is None or not ln.get_visible():
|
|
5742
5778
|
continue
|
|
5743
5779
|
xdata = np.asarray(ln.get_xdata(), float)
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5780
|
+
ydata = np.asarray(ln.get_ydata(), float)
|
|
5781
|
+
if xdata.size != ydata.size:
|
|
5782
|
+
n = int(min(xdata.size, ydata.size))
|
|
5783
|
+
if n < 3:
|
|
5784
|
+
continue
|
|
5785
|
+
xdata = xdata[:n]
|
|
5786
|
+
ydata = ydata[:n]
|
|
5787
|
+
if xdata.size < 3:
|
|
5788
|
+
continue
|
|
5789
|
+
if not hasattr(ln, '_original_xdata'):
|
|
5790
|
+
ln._original_xdata = np.array(xdata, copy=True)
|
|
5791
|
+
ln._original_ydata = np.array(ydata, copy=True)
|
|
5792
|
+
dv = np.abs(np.diff(xdata))
|
|
5793
|
+
mask = np.ones_like(xdata, dtype=bool)
|
|
5794
|
+
mask[1:] &= dv >= threshold_v
|
|
5795
|
+
mask[:-1] &= dv >= threshold_v
|
|
5796
|
+
filtered_x = xdata[mask]
|
|
5797
|
+
filtered_y = ydata[mask]
|
|
5798
|
+
before = len(xdata)
|
|
5799
|
+
after = len(filtered_x)
|
|
5800
|
+
if after < before:
|
|
5801
|
+
ln.set_xdata(filtered_x)
|
|
5802
|
+
ln.set_ydata(filtered_y)
|
|
5803
|
+
ln._smooth_applied = True
|
|
5804
|
+
filtered += 1
|
|
5805
|
+
total_before += before
|
|
5806
|
+
total_after += after
|
|
5765
5807
|
if filtered:
|
|
5766
5808
|
removed = total_before - total_after
|
|
5767
5809
|
pct = 100 * removed / total_before if total_before else 0
|
|
@@ -5867,6 +5909,12 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Optional[Dict[int, Dict[s
|
|
|
5867
5909
|
continue
|
|
5868
5910
|
xdata = np.asarray(ln.get_xdata(), float)
|
|
5869
5911
|
ydata = np.asarray(ln.get_ydata(), float)
|
|
5912
|
+
if xdata.size != ydata.size:
|
|
5913
|
+
n = int(min(xdata.size, ydata.size))
|
|
5914
|
+
if n < 3:
|
|
5915
|
+
continue
|
|
5916
|
+
xdata = xdata[:n]
|
|
5917
|
+
ydata = ydata[:n]
|
|
5870
5918
|
if xdata.size < 3:
|
|
5871
5919
|
continue
|
|
5872
5920
|
if not hasattr(ln, '_original_xdata'):
|
|
@@ -5976,6 +6024,12 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Optional[Dict[int, Dict[s
|
|
|
5976
6024
|
continue
|
|
5977
6025
|
xdata = np.asarray(ln.get_xdata(), float)
|
|
5978
6026
|
ydata = np.asarray(ln.get_ydata(), float)
|
|
6027
|
+
if xdata.size != ydata.size:
|
|
6028
|
+
n = int(min(xdata.size, ydata.size))
|
|
6029
|
+
if n < 5:
|
|
6030
|
+
continue
|
|
6031
|
+
xdata = xdata[:n]
|
|
6032
|
+
ydata = ydata[:n]
|
|
5979
6033
|
if xdata.size < 5:
|
|
5980
6034
|
continue
|
|
5981
6035
|
if not hasattr(ln, '_original_xdata'):
|
|
@@ -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.36
|
|
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.36"
|
|
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
|