batplot 1.7.26__tar.gz → 1.7.27__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.

Potentially problematic release.


This version of batplot might be problematic. Click here for more details.

Files changed (60) hide show
  1. {batplot-1.7.26/batplot.egg-info → batplot-1.7.27}/PKG-INFO +1 -1
  2. {batplot-1.7.26 → batplot-1.7.27}/batplot/__init__.py +1 -1
  3. {batplot-1.7.26 → batplot-1.7.27}/batplot/cpc_interactive.py +4 -0
  4. {batplot-1.7.26 → batplot-1.7.27}/batplot/electrochem_interactive.py +4 -0
  5. {batplot-1.7.26 → batplot-1.7.27}/batplot/interactive.py +4 -0
  6. {batplot-1.7.26 → batplot-1.7.27}/batplot/operando.py +3 -3
  7. {batplot-1.7.26 → batplot-1.7.27}/batplot/operando_ec_interactive.py +21 -0
  8. {batplot-1.7.26 → batplot-1.7.27}/batplot/session.py +18 -0
  9. {batplot-1.7.26 → batplot-1.7.27}/batplot/style.py +4 -0
  10. {batplot-1.7.26 → batplot-1.7.27}/batplot/utils.py +48 -0
  11. {batplot-1.7.26 → batplot-1.7.27/batplot.egg-info}/PKG-INFO +1 -1
  12. {batplot-1.7.26 → batplot-1.7.27}/pyproject.toml +1 -1
  13. {batplot-1.7.26 → batplot-1.7.27}/LICENSE +0 -0
  14. {batplot-1.7.26 → batplot-1.7.27}/MANIFEST.in +0 -0
  15. {batplot-1.7.26 → batplot-1.7.27}/README.md +0 -0
  16. {batplot-1.7.26 → batplot-1.7.27}/USER_MANUAL.md +0 -0
  17. {batplot-1.7.26 → batplot-1.7.27}/batplot/args.py +0 -0
  18. {batplot-1.7.26 → batplot-1.7.27}/batplot/batch.py +0 -0
  19. {batplot-1.7.26 → batplot-1.7.27}/batplot/batplot.py +0 -0
  20. {batplot-1.7.26 → batplot-1.7.27}/batplot/cif.py +0 -0
  21. {batplot-1.7.26 → batplot-1.7.27}/batplot/cli.py +0 -0
  22. {batplot-1.7.26 → batplot-1.7.27}/batplot/color_utils.py +0 -0
  23. {batplot-1.7.26 → batplot-1.7.27}/batplot/config.py +0 -0
  24. {batplot-1.7.26 → batplot-1.7.27}/batplot/converters.py +0 -0
  25. {batplot-1.7.26 → batplot-1.7.27}/batplot/data/USER_MANUAL.md +0 -0
  26. {batplot-1.7.26 → batplot-1.7.27}/batplot/manual.py +0 -0
  27. {batplot-1.7.26 → batplot-1.7.27}/batplot/modes.py +0 -0
  28. {batplot-1.7.26 → batplot-1.7.27}/batplot/plotting.py +0 -0
  29. {batplot-1.7.26 → batplot-1.7.27}/batplot/readers.py +0 -0
  30. {batplot-1.7.26 → batplot-1.7.27}/batplot/ui.py +0 -0
  31. {batplot-1.7.26 → batplot-1.7.27}/batplot/version_check.py +0 -0
  32. {batplot-1.7.26 → batplot-1.7.27}/batplot.egg-info/SOURCES.txt +0 -0
  33. {batplot-1.7.26 → batplot-1.7.27}/batplot.egg-info/dependency_links.txt +0 -0
  34. {batplot-1.7.26 → batplot-1.7.27}/batplot.egg-info/entry_points.txt +0 -0
  35. {batplot-1.7.26 → batplot-1.7.27}/batplot.egg-info/requires.txt +0 -0
  36. {batplot-1.7.26 → batplot-1.7.27}/batplot.egg-info/top_level.txt +0 -0
  37. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/__init__.py +0 -0
  38. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/args.py +0 -0
  39. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/batch.py +0 -0
  40. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/batplot.py +0 -0
  41. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/cif.py +0 -0
  42. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/cli.py +0 -0
  43. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/color_utils.py +0 -0
  44. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/config.py +0 -0
  45. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/converters.py +0 -0
  46. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/cpc_interactive.py +0 -0
  47. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/electrochem_interactive.py +0 -0
  48. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/interactive.py +0 -0
  49. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/modes.py +0 -0
  50. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/operando.py +0 -0
  51. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/operando_ec_interactive.py +0 -0
  52. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/plotting.py +0 -0
  53. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/readers.py +0 -0
  54. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/session.py +0 -0
  55. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/style.py +0 -0
  56. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/ui.py +0 -0
  57. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/utils.py +0 -0
  58. {batplot-1.7.26 → batplot-1.7.27}/batplot_backup_20251121_223043/version_check.py +0 -0
  59. {batplot-1.7.26 → batplot-1.7.27}/setup.cfg +0 -0
  60. {batplot-1.7.26 → batplot-1.7.27}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: batplot
3
- Version: 1.7.26
3
+ Version: 1.7.27
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.7.26"
3
+ __version__ = "1.7.27"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -2090,6 +2090,10 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
2090
2090
  if yn != 'y':
2091
2091
  _print_menu(); continue
2092
2092
  if target:
2093
+ # Ensure exact case is preserved (important for macOS case-insensitive filesystem)
2094
+ from .utils import ensure_exact_case_filename
2095
+ target = ensure_exact_case_filename(target)
2096
+
2093
2097
  # Save current legend position before export (savefig can change layout)
2094
2098
  saved_legend_pos = None
2095
2099
  try:
@@ -1684,6 +1684,10 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
1684
1684
  if not already_confirmed and os.path.exists(target):
1685
1685
  target = _confirm_overwrite(target)
1686
1686
  if target:
1687
+ # Ensure exact case is preserved (important for macOS case-insensitive filesystem)
1688
+ from .utils import ensure_exact_case_filename
1689
+ target = ensure_exact_case_filename(target)
1690
+
1687
1691
  # Save current legend position before export (savefig can change layout)
1688
1692
  saved_legend_pos = None
1689
1693
  try:
@@ -3709,6 +3709,10 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
3709
3709
  if not export_target:
3710
3710
  print("Export canceled.")
3711
3711
  else:
3712
+ # Ensure exact case is preserved (important for macOS case-insensitive filesystem)
3713
+ from .utils import ensure_exact_case_filename
3714
+ export_target = ensure_exact_case_filename(export_target)
3715
+
3712
3716
  # Temporarily remove numbering for export
3713
3717
  for i, txt in enumerate(label_text_objects):
3714
3718
  txt.set_text(labels[i])
@@ -386,7 +386,7 @@ def plot_operando_folder(folder: str, args) -> Tuple[plt.Figure, plt.Axes, Dict[
386
386
  x_data, y_data, current_mA, x_label, y_label = result
387
387
  # For EC-Lab files: x_label='Time (h)', y_label='Voltage (V)'
388
388
  # For simple files: x_label could be 'Time(h)', 'time', etc.
389
- # EC-Lab returns time in seconds, needs conversion to hours
389
+ # EC-Lab files: read_mpt_file already converts time from seconds to hours
390
390
  # operando plots with voltage on X-axis and time on Y-axis
391
391
 
392
392
  # Check if labels indicate time/voltage data (flexible matching)
@@ -400,8 +400,8 @@ def plot_operando_folder(folder: str, args) -> Tuple[plt.Figure, plt.Axes, Dict[
400
400
  is_time_voltage = (has_time_in_x or has_time_in_y) and (has_voltage_in_x or has_voltage_in_y)
401
401
 
402
402
  if x_label == 'Time (h)' and y_label == 'Voltage (V)':
403
- # EC-Lab file: convert time to hours and swap axes
404
- time_h = np.asarray(x_data, float) / 3600.0
403
+ # EC-Lab file: time is already in hours from read_mpt_file, just swap axes
404
+ time_h = np.asarray(x_data, float) # Already in hours, no conversion needed
405
405
  voltage_v = np.asarray(y_data, float)
406
406
  x_data = voltage_v
407
407
  y_data = time_h
@@ -1765,6 +1765,10 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
1765
1765
  target = _co(target)
1766
1766
  if not target:
1767
1767
  print_menu(); continue
1768
+ # Ensure exact case is preserved (important for macOS case-insensitive filesystem)
1769
+ from .utils import ensure_exact_case_filename
1770
+ target = ensure_exact_case_filename(target)
1771
+
1768
1772
  _, ext = os.path.splitext(target)
1769
1773
  if ext.lower() == '.svg':
1770
1774
  try:
@@ -2052,6 +2056,19 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
2052
2056
  print_menu(); continue
2053
2057
  dump_operando_session(target, fig=fig, ax=ax, im=im, cbar=cbar, ec_ax=ec_ax, skip_confirm=True)
2054
2058
  fig._last_session_save_path = target
2059
+ # Show the actual filename that was saved (in case of case differences on macOS)
2060
+ actual_name = os.path.basename(target)
2061
+ if os.path.exists(target):
2062
+ # Get the actual filename as stored on disk (for case-sensitive display)
2063
+ try:
2064
+ dir_files = os.listdir(folder)
2065
+ for f in dir_files:
2066
+ if f.lower() == actual_name.lower():
2067
+ actual_name = f
2068
+ break
2069
+ except Exception:
2070
+ pass
2071
+ print(f"Operando session saved to {os.path.join(folder, actual_name)}")
2055
2072
  except Exception as e:
2056
2073
  print(f"Save failed: {e}")
2057
2074
  print_menu(); continue
@@ -3777,6 +3794,10 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
3777
3794
  if yn != 'y':
3778
3795
  target = None
3779
3796
  if target:
3797
+ # Ensure exact case is preserved (important for macOS case-insensitive filesystem)
3798
+ from .utils import ensure_exact_case_filename
3799
+ target = ensure_exact_case_filename(target)
3800
+
3780
3801
  with open(target, 'w', encoding='utf-8') as f:
3781
3802
  json.dump(cfg, f, indent=2)
3782
3803
  print(f"Exported style to {target}")
@@ -519,6 +519,10 @@ def dump_session(
519
519
  if not target:
520
520
  print("Session save canceled.")
521
521
  return
522
+ # Ensure exact case is preserved (important for macOS case-insensitive filesystem)
523
+ from .utils import ensure_exact_case_filename
524
+ target = ensure_exact_case_filename(target)
525
+
522
526
  with open(target, 'wb') as f:
523
527
  pickle.dump(sess, f)
524
528
  print(f"Session saved to {target}")
@@ -663,6 +667,16 @@ def dump_operando_session(
663
667
  # Capture operando WASD state, spines, and tick widths
664
668
  op_wasd_state = _capture_wasd_state(ax)
665
669
  op_spines, op_ticks = _capture_spine_tick_widths(ax)
670
+
671
+ # Capture operando title offsets
672
+ op_title_offsets = {
673
+ 'top_y': float(getattr(ax, '_top_xlabel_manual_offset_y_pts', 0.0) or 0.0),
674
+ 'top_x': float(getattr(ax, '_top_xlabel_manual_offset_x_pts', 0.0) or 0.0),
675
+ 'bottom_y': float(getattr(ax, '_bottom_xlabel_manual_offset_y_pts', 0.0) or 0.0),
676
+ 'left_x': float(getattr(ax, '_left_ylabel_manual_offset_x_pts', 0.0) or 0.0),
677
+ 'right_x': float(getattr(ax, '_right_ylabel_manual_offset_x_pts', 0.0) or 0.0),
678
+ 'right_y': float(getattr(ax, '_right_ylabel_manual_offset_y_pts', 0.0) or 0.0),
679
+ }
666
680
 
667
681
  # EC panel (optional)
668
682
  ec_state = None
@@ -783,6 +797,10 @@ def dump_operando_session(
783
797
  if not target:
784
798
  print("Session save canceled.")
785
799
  return
800
+ # Ensure exact case is preserved (important for macOS case-insensitive filesystem)
801
+ from .utils import ensure_exact_case_filename
802
+ target = ensure_exact_case_filename(target)
803
+
786
804
  with open(target, 'wb') as f:
787
805
  pickle.dump(sess, f)
788
806
  print(f"Operando session saved to {target}")
@@ -787,6 +787,10 @@ def export_style_config(
787
787
  print("Style export canceled.")
788
788
  return None
789
789
 
790
+ # Ensure exact case is preserved (important for macOS case-insensitive filesystem)
791
+ from .utils import ensure_exact_case_filename
792
+ target_path = ensure_exact_case_filename(target_path)
793
+
790
794
  with open(target_path, "w", encoding="utf-8") as f:
791
795
  json.dump(cfg, f, indent=2)
792
796
  print(f"Exported style to {target_path}")
@@ -777,6 +777,54 @@ def choose_save_path(file_paths: list, purpose: str = "saving") -> Optional[str]
777
777
  return os.getcwd()
778
778
 
779
779
 
780
+ def ensure_exact_case_filename(target_path: str) -> str:
781
+ """Ensure a file is saved with the exact case specified, even on case-insensitive filesystems.
782
+
783
+ This function handles case-insensitive filesystems (macOS, Windows) by ensuring that
784
+ if a file exists with different case, it is removed first so the new file can be created
785
+ with the exact case specified by the user.
786
+
787
+ On case-sensitive filesystems (Linux, Unix), this function is safe but has no effect
788
+ since files with different case are treated as different files.
789
+
790
+ Args:
791
+ target_path: The desired file path with exact case
792
+
793
+ Returns:
794
+ The same path (for compatibility)
795
+ """
796
+ folder = os.path.dirname(target_path)
797
+ desired_basename = os.path.basename(target_path)
798
+
799
+ if not folder or not desired_basename:
800
+ return target_path
801
+
802
+ try:
803
+ # Check if file already exists with exact case
804
+ if os.path.exists(target_path):
805
+ # Check if the actual filename on disk matches the desired case
806
+ existing_files = os.listdir(folder)
807
+ for existing_file in existing_files:
808
+ # If same name (case-insensitive) but different case, we need to fix it
809
+ if existing_file.lower() == desired_basename.lower() and existing_file != desired_basename:
810
+ existing_path = os.path.join(folder, existing_file)
811
+ # Delete the existing file with wrong case
812
+ # This is safe on case-insensitive filesystems and has no effect on case-sensitive ones
813
+ try:
814
+ if os.path.exists(existing_path):
815
+ os.remove(existing_path)
816
+ except Exception:
817
+ # Ignore errors (e.g., permission issues, file in use)
818
+ pass
819
+ break
820
+ except Exception:
821
+ # If we can't check/list the directory, just return the path as-is
822
+ # This is safe and ensures we don't break on permission errors
823
+ pass
824
+
825
+ return target_path
826
+
827
+
780
828
  def _normalize_extension(ext: str) -> str:
781
829
  if not ext:
782
830
  return ext
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: batplot
3
- Version: 1.7.26
3
+ Version: 1.7.27
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.7.26"
7
+ version = "1.7.27"
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