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.
- {batplot-1.3.9 → batplot-1.3.10}/PKG-INFO +1 -1
- {batplot-1.3.9 → batplot-1.3.10}/batplot/__init__.py +1 -1
- {batplot-1.3.9 → batplot-1.3.10}/batplot/batplot.py +99 -4
- {batplot-1.3.9 → batplot-1.3.10}/batplot/cpc_interactive.py +41 -5
- {batplot-1.3.9 → batplot-1.3.10}/batplot/electrochem_interactive.py +43 -5
- {batplot-1.3.9 → batplot-1.3.10}/batplot/interactive.py +231 -180
- {batplot-1.3.9 → batplot-1.3.10}/batplot/operando.py +2 -2
- {batplot-1.3.9 → batplot-1.3.10}/batplot/operando_ec_interactive.py +201 -15
- {batplot-1.3.9 → batplot-1.3.10}/batplot/session.py +80 -20
- {batplot-1.3.9 → batplot-1.3.10}/batplot/ui.py +171 -169
- {batplot-1.3.9 → batplot-1.3.10}/batplot.egg-info/PKG-INFO +1 -1
- {batplot-1.3.9 → batplot-1.3.10}/pyproject.toml +1 -1
- {batplot-1.3.9 → batplot-1.3.10}/LICENSE +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/README.md +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot/args.py +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot/batch.py +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot/batplot_new.py +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot/cif.py +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot/cli.py +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot/converters.py +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot/modes.py +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot/plotting.py +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot/readers.py +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot/style.py +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot/utils.py +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot.egg-info/SOURCES.txt +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot.egg-info/dependency_links.txt +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot.egg-info/entry_points.txt +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot.egg-info/requires.txt +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/batplot.egg-info/top_level.txt +0 -0
- {batplot-1.3.9 → batplot-1.3.10}/setup.cfg +0 -0
- {batplot-1.3.9 → 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,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
1211
|
-
if
|
|
1212
|
-
|
|
1213
|
-
|
|
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
|
-
|
|
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
|
-
|
|
850
|
-
if
|
|
851
|
-
|
|
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
|
-
|
|
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()
|