batplot 1.3.8__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.
- {batplot-1.3.8 → batplot-1.3.10}/PKG-INFO +1 -1
- {batplot-1.3.8 → batplot-1.3.10}/batplot/__init__.py +1 -1
- {batplot-1.3.8 → batplot-1.3.10}/batplot/batplot.py +99 -4
- {batplot-1.3.8 → batplot-1.3.10}/batplot/cpc_interactive.py +58 -5
- {batplot-1.3.8 → batplot-1.3.10}/batplot/electrochem_interactive.py +57 -5
- {batplot-1.3.8 → batplot-1.3.10}/batplot/interactive.py +231 -180
- {batplot-1.3.8 → batplot-1.3.10}/batplot/operando.py +4 -3
- {batplot-1.3.8 → batplot-1.3.10}/batplot/operando_ec_interactive.py +585 -297
- {batplot-1.3.8 → batplot-1.3.10}/batplot/session.py +228 -115
- {batplot-1.3.8 → batplot-1.3.10}/batplot/ui.py +171 -169
- {batplot-1.3.8 → batplot-1.3.10}/batplot.egg-info/PKG-INFO +1 -1
- {batplot-1.3.8 → batplot-1.3.10}/pyproject.toml +1 -1
- {batplot-1.3.8 → batplot-1.3.10}/LICENSE +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/README.md +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot/args.py +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot/batch.py +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot/batplot_new.py +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot/cif.py +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot/cli.py +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot/converters.py +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot/modes.py +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot/plotting.py +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot/readers.py +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot/style.py +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot/utils.py +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot.egg-info/SOURCES.txt +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot.egg-info/dependency_links.txt +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot.egg-info/entry_points.txt +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot.egg-info/requires.txt +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/batplot.egg-info/top_level.txt +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/setup.cfg +0 -0
- {batplot-1.3.8 → batplot-1.3.10}/setup.py +0 -0
|
@@ -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,
|
|
@@ -296,6 +296,11 @@ def _style_snapshot(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_data=Non
|
|
|
296
296
|
'right': {'linewidth': ax2.spines.get('right').get_linewidth() if ax2.spines.get('right') else None,
|
|
297
297
|
'visible': ax2.spines.get('right').get_visible() if ax2.spines.get('right') else None},
|
|
298
298
|
},
|
|
299
|
+
'labelpads': {
|
|
300
|
+
'x': getattr(ax.xaxis, 'labelpad', None),
|
|
301
|
+
'ly': getattr(ax.yaxis, 'labelpad', None), # left y-axis (capacity)
|
|
302
|
+
'ry': getattr(ax2.yaxis, 'labelpad', None), # right y-axis (efficiency)
|
|
303
|
+
},
|
|
299
304
|
'series': {
|
|
300
305
|
'charge': {
|
|
301
306
|
'color': _color_of(sc_charge),
|
|
@@ -628,6 +633,18 @@ def _apply_style(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, cfg: Dict, file_
|
|
|
628
633
|
except Exception: pass
|
|
629
634
|
except Exception:
|
|
630
635
|
pass
|
|
636
|
+
# Restore labelpads
|
|
637
|
+
try:
|
|
638
|
+
pads = cfg.get('labelpads', {})
|
|
639
|
+
if pads:
|
|
640
|
+
if pads.get('x') is not None:
|
|
641
|
+
ax.xaxis.labelpad = pads['x']
|
|
642
|
+
if pads.get('ly') is not None:
|
|
643
|
+
ax.yaxis.labelpad = pads['ly']
|
|
644
|
+
if pads.get('ry') is not None:
|
|
645
|
+
ax2.yaxis.labelpad = pads['ry']
|
|
646
|
+
except Exception:
|
|
647
|
+
pass
|
|
631
648
|
try:
|
|
632
649
|
fig.canvas.draw_idle()
|
|
633
650
|
except Exception:
|
|
@@ -1187,13 +1204,46 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
1187
1204
|
continue
|
|
1188
1205
|
elif key == 'e':
|
|
1189
1206
|
try:
|
|
1190
|
-
|
|
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()
|
|
1191
1221
|
if not fname or fname.lower() == 'q':
|
|
1192
1222
|
_print_menu(); continue
|
|
1193
|
-
|
|
1194
|
-
if
|
|
1195
|
-
|
|
1196
|
-
|
|
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)
|
|
1197
1247
|
if target:
|
|
1198
1248
|
# Remove numbering from legend labels before export
|
|
1199
1249
|
original_labels = {}
|
|
@@ -2230,6 +2280,9 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
2230
2280
|
_print_menu(); continue
|
|
2231
2281
|
elif key == 'r':
|
|
2232
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$ → Å")
|
|
2233
2286
|
while True:
|
|
2234
2287
|
print("Rename titles: x=x-axis, ly=left y-axis, ry=right y-axis, q=back")
|
|
2235
2288
|
sub = input("Rename> ").strip().lower()
|
|
@@ -649,6 +649,10 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
649
649
|
'wasd_state': dict(getattr(fig, '_ec_wasd_state', {})) if hasattr(fig, '_ec_wasd_state') else {},
|
|
650
650
|
'fig_size': list(fig.get_size_inches()),
|
|
651
651
|
'rotation_angle': getattr(fig, '_ec_rotation_angle', 0),
|
|
652
|
+
'labelpads': {
|
|
653
|
+
'x': getattr(ax.xaxis, 'labelpad', None),
|
|
654
|
+
'y': getattr(ax.yaxis, 'labelpad', None),
|
|
655
|
+
},
|
|
652
656
|
'spines': {name: {
|
|
653
657
|
'lw': (ax.spines.get(name).get_linewidth() if ax.spines.get(name) else None),
|
|
654
658
|
'visible': (ax.spines.get(name).get_visible() if ax.spines.get(name) else None)
|
|
@@ -776,6 +780,16 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
776
780
|
_position_top_xlabel(); _position_right_ylabel()
|
|
777
781
|
except Exception:
|
|
778
782
|
pass
|
|
783
|
+
# Restore labelpads (for title positioning)
|
|
784
|
+
try:
|
|
785
|
+
pads = snap.get('labelpads', {})
|
|
786
|
+
if pads:
|
|
787
|
+
if pads.get('x') is not None:
|
|
788
|
+
ax.xaxis.labelpad = pads['x']
|
|
789
|
+
if pads.get('y') is not None:
|
|
790
|
+
ax.yaxis.labelpad = pads['y']
|
|
791
|
+
except Exception:
|
|
792
|
+
pass
|
|
779
793
|
# Lines (by index)
|
|
780
794
|
try:
|
|
781
795
|
if len(ax.lines) == len(snap.get('lines', [])):
|
|
@@ -828,15 +842,50 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
828
842
|
elif key == 'e':
|
|
829
843
|
# Export current figure to a file; default extension .svg if missing
|
|
830
844
|
try:
|
|
831
|
-
|
|
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()
|
|
832
859
|
if not fname or fname.lower() == 'q':
|
|
833
860
|
_print_menu(len(all_cycles), is_dqdv)
|
|
834
861
|
continue
|
|
835
|
-
|
|
836
|
-
if
|
|
837
|
-
|
|
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
|
+
|
|
838
884
|
try:
|
|
839
|
-
|
|
885
|
+
if already_confirmed:
|
|
886
|
+
target = fname
|
|
887
|
+
else:
|
|
888
|
+
target = _confirm_overwrite(fname)
|
|
840
889
|
if target:
|
|
841
890
|
# If exporting SVG, make background transparent for PowerPoint
|
|
842
891
|
_, ext2 = os.path.splitext(target)
|
|
@@ -1471,6 +1520,9 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1471
1520
|
elif key == 'r':
|
|
1472
1521
|
# Rename axis labels
|
|
1473
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$ → Å")
|
|
1474
1526
|
while True:
|
|
1475
1527
|
print("Rename axis: x, y, both, q=back")
|
|
1476
1528
|
sub = input("Rename> ").strip().lower()
|