batplot 1.3.9__tar.gz → 1.3.10__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 (32) hide show
  1. {batplot-1.3.9 → batplot-1.3.10}/PKG-INFO +1 -1
  2. {batplot-1.3.9 → batplot-1.3.10}/batplot/__init__.py +1 -1
  3. {batplot-1.3.9 → batplot-1.3.10}/batplot/batplot.py +99 -4
  4. {batplot-1.3.9 → batplot-1.3.10}/batplot/cpc_interactive.py +41 -5
  5. {batplot-1.3.9 → batplot-1.3.10}/batplot/electrochem_interactive.py +43 -5
  6. {batplot-1.3.9 → batplot-1.3.10}/batplot/interactive.py +231 -180
  7. {batplot-1.3.9 → batplot-1.3.10}/batplot/operando.py +2 -2
  8. {batplot-1.3.9 → batplot-1.3.10}/batplot/operando_ec_interactive.py +201 -15
  9. {batplot-1.3.9 → batplot-1.3.10}/batplot/session.py +80 -20
  10. {batplot-1.3.9 → batplot-1.3.10}/batplot/ui.py +171 -169
  11. {batplot-1.3.9 → batplot-1.3.10}/batplot.egg-info/PKG-INFO +1 -1
  12. {batplot-1.3.9 → batplot-1.3.10}/pyproject.toml +1 -1
  13. {batplot-1.3.9 → batplot-1.3.10}/LICENSE +0 -0
  14. {batplot-1.3.9 → batplot-1.3.10}/README.md +0 -0
  15. {batplot-1.3.9 → batplot-1.3.10}/batplot/args.py +0 -0
  16. {batplot-1.3.9 → batplot-1.3.10}/batplot/batch.py +0 -0
  17. {batplot-1.3.9 → batplot-1.3.10}/batplot/batplot_new.py +0 -0
  18. {batplot-1.3.9 → batplot-1.3.10}/batplot/cif.py +0 -0
  19. {batplot-1.3.9 → batplot-1.3.10}/batplot/cli.py +0 -0
  20. {batplot-1.3.9 → batplot-1.3.10}/batplot/converters.py +0 -0
  21. {batplot-1.3.9 → batplot-1.3.10}/batplot/modes.py +0 -0
  22. {batplot-1.3.9 → batplot-1.3.10}/batplot/plotting.py +0 -0
  23. {batplot-1.3.9 → batplot-1.3.10}/batplot/readers.py +0 -0
  24. {batplot-1.3.9 → batplot-1.3.10}/batplot/style.py +0 -0
  25. {batplot-1.3.9 → batplot-1.3.10}/batplot/utils.py +0 -0
  26. {batplot-1.3.9 → batplot-1.3.10}/batplot.egg-info/SOURCES.txt +0 -0
  27. {batplot-1.3.9 → batplot-1.3.10}/batplot.egg-info/dependency_links.txt +0 -0
  28. {batplot-1.3.9 → batplot-1.3.10}/batplot.egg-info/entry_points.txt +0 -0
  29. {batplot-1.3.9 → batplot-1.3.10}/batplot.egg-info/requires.txt +0 -0
  30. {batplot-1.3.9 → batplot-1.3.10}/batplot.egg-info/top_level.txt +0 -0
  31. {batplot-1.3.9 → batplot-1.3.10}/setup.cfg +0 -0
  32. {batplot-1.3.9 → batplot-1.3.10}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: batplot
3
- Version: 1.3.9
3
+ Version: 1.3.10
4
4
  Summary: Interactive plotting for XRD, PDF, and XAS data (.xye, .xy, .qye, .dat, .csv, .gr, .nor, .chik, .chir)
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.3.9"
3
+ __version__ = "1.3.10"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -1409,6 +1409,29 @@ def batplot_main() -> int:
1409
1409
  spm = fig_cfg.get('subplot_margins')
1410
1410
  if spm and all(k in spm for k in ('left','right','bottom','top')):
1411
1411
  fig.subplots_adjust(left=spm['left'], right=spm['right'], bottom=spm['bottom'], top=spm['top'])
1412
+
1413
+ # Restore exact frame size if stored (for precision)
1414
+ frame_size = fig_cfg.get('frame_size')
1415
+ if frame_size and isinstance(frame_size, (list, tuple)) and len(frame_size) == 2:
1416
+ target_w_in, target_h_in = map(float, frame_size)
1417
+ # Get current canvas size
1418
+ canvas_w_in, canvas_h_in = fig.get_size_inches()
1419
+ # Calculate needed fractions to achieve exact frame size
1420
+ if canvas_w_in > 0 and canvas_h_in > 0:
1421
+ # Get current position to preserve centering
1422
+ bbox = ax.get_position()
1423
+ center_x = (bbox.x0 + bbox.x1) / 2.0
1424
+ center_y = (bbox.y0 + bbox.y1) / 2.0
1425
+ # Calculate new fractions
1426
+ new_w_frac = target_w_in / canvas_w_in
1427
+ new_h_frac = target_h_in / canvas_h_in
1428
+ # Reposition to maintain centering
1429
+ new_left = center_x - new_w_frac / 2.0
1430
+ new_right = center_x + new_w_frac / 2.0
1431
+ new_bottom = center_y - new_h_frac / 2.0
1432
+ new_top = center_y + new_h_frac / 2.0
1433
+ # Apply
1434
+ fig.subplots_adjust(left=new_left, right=new_right, bottom=new_bottom, top=new_top)
1412
1435
  except Exception:
1413
1436
  pass
1414
1437
  # Font
@@ -1431,13 +1454,13 @@ def batplot_main() -> int:
1431
1454
  try:
1432
1455
  tw = sess.get('tick_widths', {})
1433
1456
  if tw.get('x_major') is not None:
1434
- ax.tick_params(axis='x', which='major', width=tw['x_major'])
1457
+ ax.tick_params(axis='x', which='major', width=float(tw['x_major']))
1435
1458
  if tw.get('x_minor') is not None:
1436
- ax.tick_params(axis='x', which='minor', width=tw['x_minor'])
1459
+ ax.tick_params(axis='x', which='minor', width=float(tw['x_minor']))
1437
1460
  if tw.get('y_major') is not None:
1438
- ax.tick_params(axis='y', which='major', width=tw['y_major'])
1461
+ ax.tick_params(axis='y', which='major', width=float(tw['y_major']))
1439
1462
  if tw.get('y_minor') is not None:
1440
- ax.tick_params(axis='y', which='minor', width=tw['y_minor'])
1463
+ ax.tick_params(axis='y', which='minor', width=float(tw['y_minor']))
1441
1464
  except Exception:
1442
1465
  pass
1443
1466
  # Tick lengths restore
@@ -1457,6 +1480,78 @@ def batplot_main() -> int:
1457
1480
  fig._tick_lengths['minor'] = minor_len
1458
1481
  except Exception:
1459
1482
  pass
1483
+
1484
+ # Restore WASD state (spine, ticks, labels, title visibility for all 4 sides)
1485
+ try:
1486
+ wasd = sess.get('wasd_state', {})
1487
+ if wasd:
1488
+ # Store the xlabel/ylabel before applying WASD (to restore hidden titles later if needed)
1489
+ stored_xlabel = ax.get_xlabel()
1490
+ stored_ylabel = ax.get_ylabel()
1491
+
1492
+ # Apply spine visibility
1493
+ for side in ('top', 'bottom', 'left', 'right'):
1494
+ state = wasd.get(side, {})
1495
+ sp = ax.spines.get(side)
1496
+ if sp and 'spine' in state:
1497
+ sp.set_visible(bool(state['spine']))
1498
+
1499
+ # Apply tick and label visibility
1500
+ for side in ('top', 'bottom', 'left', 'right'):
1501
+ state = wasd.get(side, {})
1502
+ if side in ('top', 'bottom'):
1503
+ # X-axis ticks
1504
+ tick_key = 'tick1On' if side == 'top' else 'tick2On'
1505
+ label_key = 'label1On' if side == 'top' else 'label2On'
1506
+ if 'ticks' in state:
1507
+ ax.tick_params(axis='x', which='major', **{tick_key: bool(state['ticks'])})
1508
+ if 'labels' in state:
1509
+ ax.tick_params(axis='x', which='major', **{label_key: bool(state['labels'])})
1510
+ if 'minor' in state:
1511
+ ax.tick_params(axis='x', which='minor', **{tick_key: bool(state['minor'])})
1512
+ else:
1513
+ # Y-axis ticks
1514
+ tick_key = 'tick1On' if side == 'left' else 'tick2On'
1515
+ label_key = 'label1On' if side == 'left' else 'label2On'
1516
+ if 'ticks' in state:
1517
+ ax.tick_params(axis='y', which='major', **{tick_key: bool(state['ticks'])})
1518
+ if 'labels' in state:
1519
+ ax.tick_params(axis='y', which='major', **{label_key: bool(state['labels'])})
1520
+ if 'minor' in state:
1521
+ ax.tick_params(axis='y', which='minor', **{tick_key: bool(state['minor'])})
1522
+
1523
+ # Apply title visibility - CRITICAL: Check title state before restoring labels
1524
+ # Bottom xlabel
1525
+ bottom_title_on = wasd.get('bottom', {}).get('title', True)
1526
+ if bottom_title_on:
1527
+ ax.set_xlabel(stored_xlabel)
1528
+ else:
1529
+ ax.set_xlabel('') # Hidden by user via s5
1530
+ # Store the hidden label for later restoration
1531
+ if stored_xlabel:
1532
+ setattr(ax, '_stored_xlabel', stored_xlabel)
1533
+
1534
+ # Left ylabel
1535
+ left_title_on = wasd.get('left', {}).get('title', True)
1536
+ if left_title_on:
1537
+ ax.set_ylabel(stored_ylabel)
1538
+ else:
1539
+ ax.set_ylabel('') # Hidden by user via a5
1540
+ # Store the hidden label for later restoration
1541
+ if stored_ylabel:
1542
+ setattr(ax, '_stored_ylabel', stored_ylabel)
1543
+
1544
+ # Top xlabel (if exists)
1545
+ top_title_on = wasd.get('top', {}).get('title', False)
1546
+ setattr(ax, '_top_xlabel_on', top_title_on)
1547
+
1548
+ # Right ylabel (if exists)
1549
+ right_title_on = wasd.get('right', {}).get('title', False)
1550
+ setattr(ax, '_right_ylabel_on', right_title_on)
1551
+ except Exception as e:
1552
+ # Don't fail session load if WASD restoration fails
1553
+ print(f"Warning: Could not fully restore WASD state: {e}")
1554
+
1460
1555
  # Rebuild label texts
1461
1556
  for i, lab in enumerate(labels_list):
1462
1557
  txt = ax.text(1.0, 1.0, f"{i+1}: {lab}", ha='right', va='top', transform=ax.transAxes,
@@ -1204,13 +1204,46 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
1204
1204
  continue
1205
1205
  elif key == 'e':
1206
1206
  try:
1207
- fname = input("Export filename (default .svg if no extension, q=cancel): ").strip()
1207
+ # List existing figure files
1208
+ folder = os.getcwd()
1209
+ fig_extensions = ('.svg', '.png', '.jpg', '.jpeg', '.pdf', '.eps', '.tif', '.tiff')
1210
+ files = []
1211
+ try:
1212
+ files = sorted([f for f in os.listdir(folder) if f.lower().endswith(fig_extensions)])
1213
+ except Exception:
1214
+ files = []
1215
+ if files:
1216
+ print("Existing figure files:")
1217
+ for i, f in enumerate(files, 1):
1218
+ print(f" {i}: {f}")
1219
+
1220
+ fname = input("Export filename (default .svg if no extension) or number to overwrite (q=cancel): ").strip()
1208
1221
  if not fname or fname.lower() == 'q':
1209
1222
  _print_menu(); continue
1210
- root, ext = os.path.splitext(fname)
1211
- if ext == '':
1212
- fname = fname + '.svg'
1213
- target = _confirm_overwrite(fname)
1223
+
1224
+ # Check if user selected a number
1225
+ already_confirmed = False
1226
+ if fname.isdigit() and files:
1227
+ idx = int(fname)
1228
+ if 1 <= idx <= len(files):
1229
+ name = files[idx-1]
1230
+ yn = input(f"Overwrite '{name}'? (y/n): ").strip().lower()
1231
+ if yn != 'y':
1232
+ _print_menu(); continue
1233
+ fname = name
1234
+ already_confirmed = True
1235
+ else:
1236
+ print("Invalid number.")
1237
+ _print_menu(); continue
1238
+ else:
1239
+ root, ext = os.path.splitext(fname)
1240
+ if ext == '':
1241
+ fname = fname + '.svg'
1242
+
1243
+ if already_confirmed:
1244
+ target = fname
1245
+ else:
1246
+ target = _confirm_overwrite(fname)
1214
1247
  if target:
1215
1248
  # Remove numbering from legend labels before export
1216
1249
  original_labels = {}
@@ -2247,6 +2280,9 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
2247
2280
  _print_menu(); continue
2248
2281
  elif key == 'r':
2249
2282
  # Rename axis titles
2283
+ print("Tip: Use LaTeX/mathtext for special characters:")
2284
+ print(" Subscript: H$_2$O → H₂O | Superscript: m$^2$ → m²")
2285
+ print(" Greek: $\\alpha$, $\\beta$ | Angstrom: $\\AA$ → Å")
2250
2286
  while True:
2251
2287
  print("Rename titles: x=x-axis, ly=left y-axis, ry=right y-axis, q=back")
2252
2288
  sub = input("Rename> ").strip().lower()
@@ -842,15 +842,50 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
842
842
  elif key == 'e':
843
843
  # Export current figure to a file; default extension .svg if missing
844
844
  try:
845
- fname = input("Export filename (default .svg if no extension, q=cancel): ").strip()
845
+ # List existing figure files
846
+ folder = os.getcwd()
847
+ fig_extensions = ('.svg', '.png', '.jpg', '.jpeg', '.pdf', '.eps', '.tif', '.tiff')
848
+ files = []
849
+ try:
850
+ files = sorted([f for f in os.listdir(folder) if f.lower().endswith(fig_extensions)])
851
+ except Exception:
852
+ files = []
853
+ if files:
854
+ print("Existing figure files:")
855
+ for i, f in enumerate(files, 1):
856
+ print(f" {i}: {f}")
857
+
858
+ fname = input("Export filename (default .svg if no extension) or number to overwrite (q=cancel): ").strip()
846
859
  if not fname or fname.lower() == 'q':
847
860
  _print_menu(len(all_cycles), is_dqdv)
848
861
  continue
849
- root, ext = os.path.splitext(fname)
850
- if ext == '':
851
- fname = fname + '.svg'
862
+
863
+ # Check if user selected a number
864
+ already_confirmed = False
865
+ if fname.isdigit() and files:
866
+ idx = int(fname)
867
+ if 1 <= idx <= len(files):
868
+ name = files[idx-1]
869
+ yn = input(f"Overwrite '{name}'? (y/n): ").strip().lower()
870
+ if yn != 'y':
871
+ _print_menu(len(all_cycles), is_dqdv)
872
+ continue
873
+ fname = name
874
+ already_confirmed = True
875
+ else:
876
+ print("Invalid number.")
877
+ _print_menu(len(all_cycles), is_dqdv)
878
+ continue
879
+ else:
880
+ root, ext = os.path.splitext(fname)
881
+ if ext == '':
882
+ fname = fname + '.svg'
883
+
852
884
  try:
853
- target = _confirm_overwrite(fname)
885
+ if already_confirmed:
886
+ target = fname
887
+ else:
888
+ target = _confirm_overwrite(fname)
854
889
  if target:
855
890
  # If exporting SVG, make background transparent for PowerPoint
856
891
  _, ext2 = os.path.splitext(target)
@@ -1485,6 +1520,9 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
1485
1520
  elif key == 'r':
1486
1521
  # Rename axis labels
1487
1522
  try:
1523
+ print("Tip: Use LaTeX/mathtext for special characters:")
1524
+ print(" Subscript: H$_2$O → H₂O | Superscript: m$^2$ → m²")
1525
+ print(" Greek: $\\alpha$, $\\beta$ | Angstrom: $\\AA$ → Å")
1488
1526
  while True:
1489
1527
  print("Rename axis: x, y, both, q=back")
1490
1528
  sub = input("Rename> ").strip().lower()