batplot 1.8.35__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.
Files changed (42) hide show
  1. {batplot-1.8.35/batplot.egg-info → batplot-1.8.36}/PKG-INFO +1 -1
  2. {batplot-1.8.35 → batplot-1.8.36}/batplot/__init__.py +1 -1
  3. {batplot-1.8.35 → batplot-1.8.36}/batplot/batplot.py +106 -79
  4. {batplot-1.8.35 → batplot-1.8.36}/batplot/data/CHANGELOG.md +4 -0
  5. {batplot-1.8.35 → batplot-1.8.36}/batplot/interactive.py +95 -1
  6. {batplot-1.8.35 → batplot-1.8.36}/batplot/session.py +71 -22
  7. {batplot-1.8.35 → batplot-1.8.36}/batplot/style.py +51 -7
  8. {batplot-1.8.35 → batplot-1.8.36}/batplot/utils.py +121 -0
  9. {batplot-1.8.35 → batplot-1.8.36/batplot.egg-info}/PKG-INFO +1 -1
  10. {batplot-1.8.35 → batplot-1.8.36}/pyproject.toml +1 -1
  11. {batplot-1.8.35 → batplot-1.8.36}/LICENSE +0 -0
  12. {batplot-1.8.35 → batplot-1.8.36}/MANIFEST.in +0 -0
  13. {batplot-1.8.35 → batplot-1.8.36}/NOTICE +0 -0
  14. {batplot-1.8.35 → batplot-1.8.36}/README.md +0 -0
  15. {batplot-1.8.35 → batplot-1.8.36}/batplot/args.py +0 -0
  16. {batplot-1.8.35 → batplot-1.8.36}/batplot/batch.py +0 -0
  17. {batplot-1.8.35 → batplot-1.8.36}/batplot/canvas_interactive.py +0 -0
  18. {batplot-1.8.35 → batplot-1.8.36}/batplot/cif.py +0 -0
  19. {batplot-1.8.35 → batplot-1.8.36}/batplot/cli.py +0 -0
  20. {batplot-1.8.35 → batplot-1.8.36}/batplot/color_utils.py +0 -0
  21. {batplot-1.8.35 → batplot-1.8.36}/batplot/config.py +0 -0
  22. {batplot-1.8.35 → batplot-1.8.36}/batplot/converters.py +0 -0
  23. {batplot-1.8.35 → batplot-1.8.36}/batplot/cpc_interactive.py +0 -0
  24. {batplot-1.8.35 → batplot-1.8.36}/batplot/data/USER_MANUAL.md +0 -0
  25. {batplot-1.8.35 → batplot-1.8.36}/batplot/dev_upgrade.py +0 -0
  26. {batplot-1.8.35 → batplot-1.8.36}/batplot/electrochem_interactive.py +0 -0
  27. {batplot-1.8.35 → batplot-1.8.36}/batplot/manual.py +0 -0
  28. {batplot-1.8.35 → batplot-1.8.36}/batplot/modes.py +0 -0
  29. {batplot-1.8.35 → batplot-1.8.36}/batplot/operando.py +0 -0
  30. {batplot-1.8.35 → batplot-1.8.36}/batplot/operando_ec_interactive.py +0 -0
  31. {batplot-1.8.35 → batplot-1.8.36}/batplot/plotting.py +0 -0
  32. {batplot-1.8.35 → batplot-1.8.36}/batplot/readers.py +0 -0
  33. {batplot-1.8.35 → batplot-1.8.36}/batplot/showcol.py +0 -0
  34. {batplot-1.8.35 → batplot-1.8.36}/batplot/ui.py +0 -0
  35. {batplot-1.8.35 → batplot-1.8.36}/batplot/version_check.py +0 -0
  36. {batplot-1.8.35 → batplot-1.8.36}/batplot.egg-info/SOURCES.txt +0 -0
  37. {batplot-1.8.35 → batplot-1.8.36}/batplot.egg-info/dependency_links.txt +0 -0
  38. {batplot-1.8.35 → batplot-1.8.36}/batplot.egg-info/entry_points.txt +0 -0
  39. {batplot-1.8.35 → batplot-1.8.36}/batplot.egg-info/requires.txt +0 -0
  40. {batplot-1.8.35 → batplot-1.8.36}/batplot.egg-info/top_level.txt +0 -0
  41. {batplot-1.8.35 → batplot-1.8.36}/setup.cfg +0 -0
  42. {batplot-1.8.35 → 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.35
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
@@ -1,5 +1,5 @@
1
1
  """batplot: Interactive plotting for battery data visualization."""
2
2
 
3
- __version__ = "1.8.35"
3
+ __version__ = "1.8.36"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -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 _confirm_overwrite, normalize_label_text, natural_sort_key, ensure_subdirectory
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
- # Calculate base and spacing based on FIXED y-axis limits (not current)
3415
- # This prevents incremental movement when toggling
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*fixed_yr; spacing = 0.05*fixed_yr
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*fixed_yr; spacing = 0.04*fixed_yr
3422
-
3423
- # Only adjust y-axis limits if titles are visible
3424
- needed_min = base - (len(cif_tick_series)-1)*spacing - 0.04*fixed_yr
3425
- if show_titles_local and needed_min < fixed_ylim[0]:
3426
- # Expand y-axis only if needed, using fixed limits as reference
3427
- ax.set_ylim(needed_min, fixed_ylim[1])
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
- # Restore to fixed limits if no expansion needed
3430
- ax.set_ylim(fixed_ylim)
3431
-
3432
- # Get current limits for drawing (after potential expansion)
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+0.02*yr], color=color, lw=1.0, alpha=0.9, zorder=3)
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
- # Use same color as tick line
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
- # Align CIF title baseline with tick baseline for this row
3486
- txt = ax.text(prev_xlim[0], y_line, label_text,
3487
- ha='left', va='bottom', fontsize=max(8,int(0.55*plt.rcParams.get('font.size',16))), color=color)
3488
- new_art.append(txt)
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 args.stack or len(y_data_list) > 1:
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*fixed_yr; spacing = 0.05*fixed_yr
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*fixed_yr; spacing = 0.04*fixed_yr
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 - 0.04*fixed_yr
4510
- if show_titles and needed_min < fixed_ylim[0]:
4511
- # Expand y-axis only if needed, using fixed limits as reference
4512
- ax.set_ylim(needed_min, fixed_ylim[1])
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
- # Restore to fixed limits if no expansion needed
4515
- ax.set_ylim(fixed_ylim)
4516
-
4517
- # Get current limits for drawing (after potential expansion)
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
- # Align CIF title baseline with tick baseline for this row
4563
- txt = ax.text(prev_xlim[0], y_line, label_text,
4564
- ha='left', va='bottom', fontsize=max(8,int(0.55*plt.rcParams.get('font.size',12))), color=color)
4565
- new_art.append(txt)
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+0.02*yr], color=color, lw=1.0, alpha=0.9, zorder=3)
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, y_line+0.022*yr, lbl, ha='center', va='bottom', fontsize=7, rotation=90, color=color)
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+0.02*yr], color=color, lw=1.0, alpha=0.9, zorder=3)
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
- # Align CIF title baseline with tick baseline for this row
4610
- txt = ax.text(prev_xlim[0], y_line, label_text,
4611
- ha='left', va='bottom', fontsize=max(8,int(0.55*plt.rcParams.get('font.size',12))), color=color)
4612
- new_art.append(txt)
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
- # Restore y-axis: if titles are hidden, always restore; if titles are shown, only restore if we didn't need to expand
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 logic as above)
4647
- if args.stack or len(y_data_list) > 1:
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*yr; spacing = 0.05*yr
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*yr; spacing = 0.04*yr
4653
- y_line = base - i*spacing
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:
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.8.36] - 2026-03-30
4
+ - Bug fixes
5
+
6
+
3
7
  ## [1.8.35] - 2026-03-26
4
8
  - Bug fixes
5
9
 
@@ -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 _confirm_overwrite, ensure_exact_case_filename
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
- if saved_stack or len(y_data_list) > 1:
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
- spacing = 0.04 * fixed_yr
4061
- needed_min = base - (len(cif_tick_series) - 1) * spacing - 0.04 * fixed_yr
4062
- if show_titles_local and needed_min < fixed_ylim[0]:
4063
- ax.set_ylim(needed_min, fixed_ylim[1])
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
- ax.set_ylim(fixed_ylim)
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 + 0.02 * yr], color=color, lw=1.0, alpha=0.9, zorder=3)
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, y_line + 0.022 * yr, lbl, ha='center', va='bottom',
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
- txt = ax.text(prev_xlim[0], y_line, label_text,
4110
- ha='left', va='bottom',
4111
- fontsize=max(8, int(0.55 * plt.rcParams.get('font.size', 16))), color=color)
4112
- new_art.append(txt)
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, j) ---
534
- if show_cif_hkl is not None:
535
- print(f"\n--- CIF (z, j) ---")
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
- print(f"\n--- CIF (z, j) ---")
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.35
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.35"
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