batplot 1.7.23__py3-none-any.whl → 1.7.25__py3-none-any.whl
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/__init__.py +1 -1
- batplot/batplot.py +2 -2
- batplot/cpc_interactive.py +938 -325
- batplot/electrochem_interactive.py +153 -15
- batplot/interactive.py +157 -31
- batplot/operando_ec_interactive.py +163 -12
- batplot/session.py +205 -17
- batplot/style.py +109 -47
- {batplot-1.7.23.dist-info → batplot-1.7.25.dist-info}/METADATA +23 -2
- {batplot-1.7.23.dist-info → batplot-1.7.25.dist-info}/RECORD +14 -14
- {batplot-1.7.23.dist-info → batplot-1.7.25.dist-info}/WHEEL +0 -0
- {batplot-1.7.23.dist-info → batplot-1.7.25.dist-info}/entry_points.txt +0 -0
- {batplot-1.7.23.dist-info → batplot-1.7.25.dist-info}/licenses/LICENSE +0 -0
- {batplot-1.7.23.dist-info → batplot-1.7.25.dist-info}/top_level.txt +0 -0
|
@@ -1716,13 +1716,31 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1716
1716
|
else:
|
|
1717
1717
|
print(f" {i}: {fname}")
|
|
1718
1718
|
|
|
1719
|
-
|
|
1719
|
+
last_figure_path = getattr(fig, '_last_figure_export_path', None)
|
|
1720
|
+
if last_figure_path:
|
|
1721
|
+
fname = input("Export filename (default .svg if no extension), number to overwrite, or o to overwrite last (q=cancel): ").strip()
|
|
1722
|
+
else:
|
|
1723
|
+
fname = input("Export filename (default .svg if no extension) or number to overwrite (q=cancel): ").strip()
|
|
1720
1724
|
if not fname or fname.lower() == 'q':
|
|
1721
1725
|
print_menu(); continue
|
|
1722
1726
|
|
|
1727
|
+
already_confirmed = False # Initialize for new filename case
|
|
1728
|
+
# Check for 'o' option
|
|
1729
|
+
if fname.lower() == 'o':
|
|
1730
|
+
if not last_figure_path:
|
|
1731
|
+
print("No previous export found.")
|
|
1732
|
+
print_menu(); continue
|
|
1733
|
+
if not os.path.exists(last_figure_path):
|
|
1734
|
+
print(f"Previous export file not found: {last_figure_path}")
|
|
1735
|
+
print_menu(); continue
|
|
1736
|
+
yn = input(f"Overwrite '{os.path.basename(last_figure_path)}'? (y/n): ").strip().lower()
|
|
1737
|
+
if yn != 'y':
|
|
1738
|
+
print_menu(); continue
|
|
1739
|
+
target = last_figure_path
|
|
1740
|
+
already_confirmed = True
|
|
1723
1741
|
# Check if user selected a number
|
|
1724
|
-
|
|
1725
|
-
|
|
1742
|
+
elif fname.isdigit() and files:
|
|
1743
|
+
already_confirmed = False
|
|
1726
1744
|
idx = int(fname)
|
|
1727
1745
|
if 1 <= idx <= len(files):
|
|
1728
1746
|
name = files[idx-1]
|
|
@@ -1782,6 +1800,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1782
1800
|
else:
|
|
1783
1801
|
fig.savefig(target, dpi=300)
|
|
1784
1802
|
print(f"Exported figure to {target}")
|
|
1803
|
+
fig._last_figure_export_path = target
|
|
1785
1804
|
except Exception as e:
|
|
1786
1805
|
print(f"Export failed: {e}")
|
|
1787
1806
|
print_menu(); continue
|
|
@@ -1985,10 +2004,28 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1985
2004
|
print(f" {i}: {f} ({timestamp})")
|
|
1986
2005
|
else:
|
|
1987
2006
|
print(f" {i}: {f}")
|
|
1988
|
-
|
|
2007
|
+
last_session_path = getattr(fig, '_last_session_save_path', None)
|
|
2008
|
+
if last_session_path:
|
|
2009
|
+
prompt = "Enter new filename (no ext needed), number to overwrite, or o to overwrite last (q=cancel): "
|
|
2010
|
+
else:
|
|
2011
|
+
prompt = "Enter new filename (no ext needed) or number to overwrite (q=cancel): "
|
|
1989
2012
|
choice = input(prompt).strip()
|
|
1990
2013
|
if not choice or choice.lower() == 'q':
|
|
1991
2014
|
print_menu(); continue
|
|
2015
|
+
if choice.lower() == 'o':
|
|
2016
|
+
# Overwrite last saved session
|
|
2017
|
+
if not last_session_path:
|
|
2018
|
+
print("No previous save found.")
|
|
2019
|
+
print_menu(); continue
|
|
2020
|
+
if not os.path.exists(last_session_path):
|
|
2021
|
+
print(f"Previous save file not found: {last_session_path}")
|
|
2022
|
+
print_menu(); continue
|
|
2023
|
+
yn = input(f"Overwrite '{os.path.basename(last_session_path)}'? (y/n): ").strip().lower()
|
|
2024
|
+
if yn != 'y':
|
|
2025
|
+
print_menu(); continue
|
|
2026
|
+
dump_operando_session(last_session_path, fig=fig, ax=ax, im=im, cbar=cbar, ec_ax=ec_ax, skip_confirm=True)
|
|
2027
|
+
print(f"Overwritten session to {last_session_path}")
|
|
2028
|
+
print_menu(); continue
|
|
1992
2029
|
if choice.isdigit() and files:
|
|
1993
2030
|
idx = int(choice)
|
|
1994
2031
|
if 1 <= idx <= len(files):
|
|
@@ -1997,10 +2034,13 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
1997
2034
|
if yn != 'y':
|
|
1998
2035
|
print_menu(); continue
|
|
1999
2036
|
target = os.path.join(folder, name)
|
|
2037
|
+
dump_operando_session(target, fig=fig, ax=ax, im=im, cbar=cbar, ec_ax=ec_ax, skip_confirm=True)
|
|
2038
|
+
fig._last_session_save_path = target
|
|
2039
|
+
print_menu(); continue
|
|
2000
2040
|
else:
|
|
2001
2041
|
print("Invalid number.")
|
|
2002
2042
|
print_menu(); continue
|
|
2003
|
-
|
|
2043
|
+
if choice.lower() != 'o':
|
|
2004
2044
|
name = choice
|
|
2005
2045
|
root, ext = os.path.splitext(name)
|
|
2006
2046
|
if ext == '':
|
|
@@ -2010,7 +2050,8 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
2010
2050
|
yn = input(f"'{os.path.basename(target)}' exists. Overwrite? (y/n): ").strip().lower()
|
|
2011
2051
|
if yn != 'y':
|
|
2012
2052
|
print_menu(); continue
|
|
2013
|
-
|
|
2053
|
+
dump_operando_session(target, fig=fig, ax=ax, im=im, cbar=cbar, ec_ax=ec_ax, skip_confirm=True)
|
|
2054
|
+
fig._last_session_save_path = target
|
|
2014
2055
|
except Exception as e:
|
|
2015
2056
|
print(f"Save failed: {e}")
|
|
2016
2057
|
print_menu(); continue
|
|
@@ -3463,6 +3504,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3463
3504
|
else:
|
|
3464
3505
|
print(f" {_i}: {fname}")
|
|
3465
3506
|
|
|
3507
|
+
last_style_path = getattr(fig, '_last_style_export_path', None)
|
|
3466
3508
|
if ec_ax is None:
|
|
3467
3509
|
print("\nNote: Style export (.bps/.bpsg) is only available in dual-pane mode (with EC file).")
|
|
3468
3510
|
sub = input("Style submenu: (q=return, r=refresh): ").strip().lower()
|
|
@@ -3474,11 +3516,38 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3474
3516
|
print("Unknown choice.")
|
|
3475
3517
|
continue
|
|
3476
3518
|
else:
|
|
3477
|
-
|
|
3519
|
+
if last_style_path:
|
|
3520
|
+
sub = input("Style submenu: (e=export, o=overwrite last, q=return, r=refresh): ").strip().lower()
|
|
3521
|
+
else:
|
|
3522
|
+
sub = input("Style submenu: (e=export, q=return, r=refresh): ").strip().lower()
|
|
3478
3523
|
if sub == 'q':
|
|
3479
3524
|
break
|
|
3480
3525
|
if sub == 'r' or sub == '':
|
|
3481
3526
|
continue
|
|
3527
|
+
if sub == 'o':
|
|
3528
|
+
# Overwrite last exported style file
|
|
3529
|
+
if not last_style_path:
|
|
3530
|
+
print("No previous export found.")
|
|
3531
|
+
continue
|
|
3532
|
+
if not os.path.exists(last_style_path):
|
|
3533
|
+
print(f"Previous export file not found: {last_style_path}")
|
|
3534
|
+
continue
|
|
3535
|
+
yn = input(f"Overwrite '{os.path.basename(last_style_path)}'? (y/n): ").strip().lower()
|
|
3536
|
+
if yn != 'y':
|
|
3537
|
+
continue
|
|
3538
|
+
# Determine export type from existing file and rebuild config
|
|
3539
|
+
try:
|
|
3540
|
+
with open(last_style_path, 'r', encoding='utf-8') as f:
|
|
3541
|
+
old_cfg = json.load(f)
|
|
3542
|
+
old_kind = old_cfg.get('kind', '')
|
|
3543
|
+
# Need to rebuild the full config - this requires the same logic as 'e' command
|
|
3544
|
+
# For simplicity, redirect user to use 'e' for now, or we could duplicate the config building code
|
|
3545
|
+
print("To overwrite with current style, please use 'e' to export fresh.")
|
|
3546
|
+
print("The 'o' option will be enhanced in a future update to rebuild config automatically.")
|
|
3547
|
+
continue
|
|
3548
|
+
except Exception as e:
|
|
3549
|
+
print(f"Error reading previous export: {e}")
|
|
3550
|
+
continue
|
|
3482
3551
|
if sub == 'e':
|
|
3483
3552
|
# Ask for ps or psg
|
|
3484
3553
|
print("Export options:")
|
|
@@ -3598,6 +3667,23 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3598
3667
|
'x': getattr(ec_ax.xaxis, 'labelpad', None),
|
|
3599
3668
|
'y': getattr(ec_ax.yaxis, 'labelpad', None),
|
|
3600
3669
|
}
|
|
3670
|
+
# Capture title offsets
|
|
3671
|
+
op_title_offsets = {
|
|
3672
|
+
'top_y': float(getattr(ax, '_top_xlabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
3673
|
+
'top_x': float(getattr(ax, '_top_xlabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
3674
|
+
'bottom_y': float(getattr(ax, '_bottom_xlabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
3675
|
+
'left_x': float(getattr(ax, '_left_ylabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
3676
|
+
'right_x': float(getattr(ax, '_right_ylabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
3677
|
+
'right_y': float(getattr(ax, '_right_ylabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
3678
|
+
}
|
|
3679
|
+
ec_title_offsets = {
|
|
3680
|
+
'top_y': float(getattr(ec_ax, '_top_xlabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
3681
|
+
'top_x': float(getattr(ec_ax, '_top_xlabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
3682
|
+
'bottom_y': float(getattr(ec_ax, '_bottom_xlabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
3683
|
+
'left_x': float(getattr(ec_ax, '_left_ylabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
3684
|
+
'right_x': float(getattr(ec_ax, '_right_ylabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
3685
|
+
'right_y': float(getattr(ec_ax, '_right_ylabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
3686
|
+
}
|
|
3601
3687
|
|
|
3602
3688
|
if exp_choice == 'ps':
|
|
3603
3689
|
cb_h_offset = getattr(cbar.ax, '_cb_h_offset_in', 0.0)
|
|
@@ -3607,8 +3693,8 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3607
3693
|
'version': 2,
|
|
3608
3694
|
'figure': {'canvas_size': [fig_w, fig_h], 'cb_visible': cb_visible, 'cb_label_mode': cb_label_mode},
|
|
3609
3695
|
'geometry': {'op_w_in': ax_w_in, 'op_h_in': ax_h_in, 'ec_w_in': ec_w_in, 'cb_h_offset': float(cb_h_offset), 'ec_h_offset': float(ec_h_offset) if ec_h_offset is not None else None},
|
|
3610
|
-
'operando': {'cmap': cmap_name, 'wasd_state': op_wasd_state, 'spines': op_spines, 'ticks': {'widths': op_ticks}, 'y_reversed': op_reversed, 'intensity_range': intensity_range, 'labelpads': op_labelpads},
|
|
3611
|
-
'ec': {'wasd_state': ec_wasd_state, 'spines': ec_spines, 'ticks': {'widths': ec_ticks}, 'curve': ec_curve, 'y_reversed': ec_reversed, 'y_mode': ec_y_mode, 'ion_params': ion_params, 'visible': ec_visible, 'labelpads': ec_labelpads},
|
|
3696
|
+
'operando': {'cmap': cmap_name, 'wasd_state': op_wasd_state, 'spines': op_spines, 'ticks': {'widths': op_ticks}, 'y_reversed': op_reversed, 'intensity_range': intensity_range, 'labelpads': op_labelpads, 'title_offsets': op_title_offsets},
|
|
3697
|
+
'ec': {'wasd_state': ec_wasd_state, 'spines': ec_spines, 'ticks': {'widths': ec_ticks}, 'curve': ec_curve, 'y_reversed': ec_reversed, 'y_mode': ec_y_mode, 'ion_params': ion_params, 'visible': ec_visible, 'labelpads': ec_labelpads, 'title_offsets': ec_title_offsets},
|
|
3612
3698
|
'font': {'family': fam, 'size': fsize},
|
|
3613
3699
|
'colorbar': {'label': cb_label_text, 'mode': cb_label_mode, 'visible': cb_visible},
|
|
3614
3700
|
}
|
|
@@ -3621,8 +3707,8 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
3621
3707
|
'version': 2,
|
|
3622
3708
|
'figure': {'canvas_size': [fig_w, fig_h], 'cb_visible': cb_visible, 'cb_label_mode': cb_label_mode},
|
|
3623
3709
|
'geometry': {'op_w_in': ax_w_in, 'op_h_in': ax_h_in, 'ec_w_in': ec_w_in, 'cb_h_offset': float(cb_h_offset), 'ec_h_offset': float(ec_h_offset) if ec_h_offset is not None else None},
|
|
3624
|
-
'operando': {'cmap': cmap_name, 'wasd_state': op_wasd_state, 'spines': op_spines, 'ticks': {'widths': op_ticks}, 'y_reversed': op_reversed, 'intensity_range': intensity_range, 'labelpads': op_labelpads},
|
|
3625
|
-
'ec': {'wasd_state': ec_wasd_state, 'spines': ec_spines, 'ticks': {'widths': ec_ticks}, 'curve': ec_curve, 'y_reversed': ec_reversed, 'y_mode': ec_y_mode, 'ion_params': ion_params, 'visible': ec_visible, 'labelpads': ec_labelpads},
|
|
3710
|
+
'operando': {'cmap': cmap_name, 'wasd_state': op_wasd_state, 'spines': op_spines, 'ticks': {'widths': op_ticks}, 'y_reversed': op_reversed, 'intensity_range': intensity_range, 'labelpads': op_labelpads, 'title_offsets': op_title_offsets},
|
|
3711
|
+
'ec': {'wasd_state': ec_wasd_state, 'spines': ec_spines, 'ticks': {'widths': ec_ticks}, 'curve': ec_curve, 'y_reversed': ec_reversed, 'y_mode': ec_y_mode, 'ion_params': ion_params, 'visible': ec_visible, 'labelpads': ec_labelpads, 'title_offsets': ec_title_offsets},
|
|
3626
3712
|
'font': {'family': fam, 'size': fsize},
|
|
3627
3713
|
'axes_geometry': _get_geometry_snapshot(ax, ec_ax),
|
|
3628
3714
|
'colorbar': {'label': cb_label_text, 'mode': cb_label_mode, 'visible': cb_visible},
|
|
@@ -4223,6 +4309,33 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4223
4309
|
except Exception:
|
|
4224
4310
|
pass
|
|
4225
4311
|
|
|
4312
|
+
# Restore title offsets BEFORE applying labelpads
|
|
4313
|
+
if version >= 2:
|
|
4314
|
+
try:
|
|
4315
|
+
op_offsets = op.get('title_offsets', {})
|
|
4316
|
+
if op_offsets:
|
|
4317
|
+
ax._top_xlabel_manual_offset_y_pts = float(op_offsets.get('top_y', 0.0) or 0.0)
|
|
4318
|
+
ax._top_xlabel_manual_offset_x_pts = float(op_offsets.get('top_x', 0.0) or 0.0)
|
|
4319
|
+
ax._bottom_xlabel_manual_offset_y_pts = float(op_offsets.get('bottom_y', 0.0) or 0.0)
|
|
4320
|
+
ax._left_ylabel_manual_offset_x_pts = float(op_offsets.get('left_x', 0.0) or 0.0)
|
|
4321
|
+
ax._right_ylabel_manual_offset_x_pts = float(op_offsets.get('right_x', 0.0) or 0.0)
|
|
4322
|
+
ax._right_ylabel_manual_offset_y_pts = float(op_offsets.get('right_y', 0.0) or 0.0)
|
|
4323
|
+
except Exception as e:
|
|
4324
|
+
print(f"Warning: Could not apply operando title offsets: {e}")
|
|
4325
|
+
|
|
4326
|
+
try:
|
|
4327
|
+
ec_cfg = cfg.get('ec', {})
|
|
4328
|
+
ec_offsets = ec_cfg.get('title_offsets', {})
|
|
4329
|
+
if ec_offsets and ec_ax is not None:
|
|
4330
|
+
ec_ax._top_xlabel_manual_offset_y_pts = float(ec_offsets.get('top_y', 0.0) or 0.0)
|
|
4331
|
+
ec_ax._top_xlabel_manual_offset_x_pts = float(ec_offsets.get('top_x', 0.0) or 0.0)
|
|
4332
|
+
ec_ax._bottom_xlabel_manual_offset_y_pts = float(ec_offsets.get('bottom_y', 0.0) or 0.0)
|
|
4333
|
+
ec_ax._left_ylabel_manual_offset_x_pts = float(ec_offsets.get('left_x', 0.0) or 0.0)
|
|
4334
|
+
ec_ax._right_ylabel_manual_offset_x_pts = float(ec_offsets.get('right_x', 0.0) or 0.0)
|
|
4335
|
+
ec_ax._right_ylabel_manual_offset_y_pts = float(ec_offsets.get('right_y', 0.0) or 0.0)
|
|
4336
|
+
except Exception as e:
|
|
4337
|
+
print(f"Warning: Could not apply EC title offsets: {e}")
|
|
4338
|
+
|
|
4226
4339
|
# Apply labelpads (title positioning) - preserve current if not in config
|
|
4227
4340
|
if version >= 2:
|
|
4228
4341
|
try:
|
|
@@ -4266,6 +4379,44 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4266
4379
|
except Exception as e:
|
|
4267
4380
|
print(f"Warning: Could not apply EC labelpads: {e}")
|
|
4268
4381
|
|
|
4382
|
+
# Reposition titles to apply offsets (after labelpads are set)
|
|
4383
|
+
try:
|
|
4384
|
+
from .ui import position_top_xlabel as _ui_position_top_xlabel, position_bottom_xlabel as _ui_position_bottom_xlabel, position_left_ylabel as _ui_position_left_ylabel, position_right_ylabel as _ui_position_right_ylabel
|
|
4385
|
+
# Build tick_state for operando pane
|
|
4386
|
+
op_ts = getattr(ax, '_saved_tick_state', {})
|
|
4387
|
+
op_tick_state = {
|
|
4388
|
+
't_ticks': bool(op_ts.get('t_ticks', op_ts.get('tx', False))),
|
|
4389
|
+
't_labels': bool(op_ts.get('t_labels', op_ts.get('tx', False))),
|
|
4390
|
+
'b_ticks': bool(op_ts.get('b_ticks', op_ts.get('bx', True))),
|
|
4391
|
+
'b_labels': bool(op_ts.get('b_labels', op_ts.get('bx', True))),
|
|
4392
|
+
'l_ticks': bool(op_ts.get('l_ticks', op_ts.get('ly', True))),
|
|
4393
|
+
'l_labels': bool(op_ts.get('l_labels', op_ts.get('ly', True))),
|
|
4394
|
+
'r_ticks': bool(op_ts.get('r_ticks', op_ts.get('ry', False))),
|
|
4395
|
+
'r_labels': bool(op_ts.get('r_labels', op_ts.get('ry', False))),
|
|
4396
|
+
}
|
|
4397
|
+
_ui_position_top_xlabel(ax, fig, op_tick_state)
|
|
4398
|
+
_ui_position_bottom_xlabel(ax, fig, op_tick_state)
|
|
4399
|
+
_ui_position_left_ylabel(ax, fig, op_tick_state)
|
|
4400
|
+
_ui_position_right_ylabel(ax, fig, op_tick_state)
|
|
4401
|
+
if ec_ax is not None:
|
|
4402
|
+
ec_ts = getattr(ec_ax, '_saved_tick_state', {})
|
|
4403
|
+
ec_tick_state = {
|
|
4404
|
+
't_ticks': bool(ec_ts.get('t_ticks', ec_ts.get('tx', False))),
|
|
4405
|
+
't_labels': bool(ec_ts.get('t_labels', ec_ts.get('tx', False))),
|
|
4406
|
+
'b_ticks': bool(ec_ts.get('b_ticks', ec_ts.get('bx', True))),
|
|
4407
|
+
'b_labels': bool(ec_ts.get('b_labels', ec_ts.get('bx', True))),
|
|
4408
|
+
'l_ticks': bool(ec_ts.get('l_ticks', ec_ts.get('ly', True))),
|
|
4409
|
+
'l_labels': bool(ec_ts.get('l_labels', ec_ts.get('ly', True))),
|
|
4410
|
+
'r_ticks': bool(ec_ts.get('r_ticks', ec_ts.get('ry', False))),
|
|
4411
|
+
'r_labels': bool(ec_ts.get('r_labels', ec_ts.get('ry', False))),
|
|
4412
|
+
}
|
|
4413
|
+
_ui_position_top_xlabel(ec_ax, fig, ec_tick_state)
|
|
4414
|
+
_ui_position_bottom_xlabel(ec_ax, fig, ec_tick_state)
|
|
4415
|
+
_ui_position_left_ylabel(ec_ax, fig, ec_tick_state)
|
|
4416
|
+
_ui_position_right_ylabel(ec_ax, fig, ec_tick_state)
|
|
4417
|
+
except Exception as e:
|
|
4418
|
+
print(f"Warning: Could not reposition titles: {e}")
|
|
4419
|
+
|
|
4269
4420
|
# Final redraw
|
|
4270
4421
|
try:
|
|
4271
4422
|
fig.canvas.draw()
|
|
@@ -4314,7 +4465,7 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax, file_paths=None):
|
|
|
4314
4465
|
print("Rename Operando Axes: x=rename X label, y=rename Y label, q=back")
|
|
4315
4466
|
print("Tip: Use LaTeX/mathtext for special characters:")
|
|
4316
4467
|
print(" Subscript: H$_2$O → H₂O | Superscript: m$^2$ → m²")
|
|
4317
|
-
print(" Greek: $\\alpha$, $\\beta$ | Angstrom: $\\AA$ → Å")
|
|
4468
|
+
print(" Bullet: $\\bullet$ → • | Greek: $\\alpha$, $\\beta$ | Angstrom: $\\AA$ → Å")
|
|
4318
4469
|
while True:
|
|
4319
4470
|
sub = input("or> ").strip().lower()
|
|
4320
4471
|
if not sub:
|
batplot/session.py
CHANGED
|
@@ -701,6 +701,16 @@ def dump_operando_session(
|
|
|
701
701
|
ec_wasd_state = _capture_wasd_state(ec_ax)
|
|
702
702
|
ec_spines, ec_ticks = _capture_spine_tick_widths(ec_ax)
|
|
703
703
|
|
|
704
|
+
# Capture EC title offsets
|
|
705
|
+
ec_title_offsets = {
|
|
706
|
+
'top_y': float(getattr(ec_ax, '_top_xlabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
707
|
+
'top_x': float(getattr(ec_ax, '_top_xlabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
708
|
+
'bottom_y': float(getattr(ec_ax, '_bottom_xlabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
709
|
+
'left_x': float(getattr(ec_ax, '_left_ylabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
710
|
+
'right_x': float(getattr(ec_ax, '_right_ylabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
711
|
+
'right_y': float(getattr(ec_ax, '_right_ylabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
712
|
+
}
|
|
713
|
+
|
|
704
714
|
ec_state = {
|
|
705
715
|
'time_h': time_h,
|
|
706
716
|
'volt_v': volt_v,
|
|
@@ -716,6 +726,7 @@ def dump_operando_session(
|
|
|
716
726
|
'wasd_state': ec_wasd_state,
|
|
717
727
|
'spines': ec_spines,
|
|
718
728
|
'ticks': {'widths': ec_ticks},
|
|
729
|
+
'title_offsets': ec_title_offsets,
|
|
719
730
|
'stored_ylabel': getattr(ec_ax, '_stored_ylabel', None), # Save hidden ylabel text
|
|
720
731
|
'visible': bool(ec_ax.get_visible()),
|
|
721
732
|
}
|
|
@@ -750,6 +761,7 @@ def dump_operando_session(
|
|
|
750
761
|
'wasd_state': op_wasd_state,
|
|
751
762
|
'spines': op_spines,
|
|
752
763
|
'ticks': {'widths': op_ticks},
|
|
764
|
+
'title_offsets': op_title_offsets,
|
|
753
765
|
'stored_ylabel': getattr(ax, '_stored_ylabel', None), # Save hidden ylabel text
|
|
754
766
|
},
|
|
755
767
|
'colorbar': {
|
|
@@ -939,6 +951,19 @@ def load_operando_session(filename: str):
|
|
|
939
951
|
stored_ylabel = op.get('stored_ylabel')
|
|
940
952
|
if stored_ylabel is not None:
|
|
941
953
|
setattr(ax, '_stored_ylabel', stored_ylabel)
|
|
954
|
+
|
|
955
|
+
# Restore operando title offsets
|
|
956
|
+
try:
|
|
957
|
+
op_title_offsets = op.get('title_offsets', {})
|
|
958
|
+
if op_title_offsets:
|
|
959
|
+
ax._top_xlabel_manual_offset_y_pts = float(op_title_offsets.get('top_y', 0.0) or 0.0)
|
|
960
|
+
ax._top_xlabel_manual_offset_x_pts = float(op_title_offsets.get('top_x', 0.0) or 0.0)
|
|
961
|
+
ax._bottom_xlabel_manual_offset_y_pts = float(op_title_offsets.get('bottom_y', 0.0) or 0.0)
|
|
962
|
+
ax._left_ylabel_manual_offset_x_pts = float(op_title_offsets.get('left_x', 0.0) or 0.0)
|
|
963
|
+
ax._right_ylabel_manual_offset_x_pts = float(op_title_offsets.get('right_x', 0.0) or 0.0)
|
|
964
|
+
ax._right_ylabel_manual_offset_y_pts = float(op_title_offsets.get('right_y', 0.0) or 0.0)
|
|
965
|
+
except Exception:
|
|
966
|
+
pass
|
|
942
967
|
|
|
943
968
|
# Apply operando spines
|
|
944
969
|
op_spines = op.get('spines', {})
|
|
@@ -1189,6 +1214,19 @@ def load_operando_session(filename: str):
|
|
|
1189
1214
|
if stored_ylabel is not None:
|
|
1190
1215
|
setattr(ec_ax, '_stored_ylabel', stored_ylabel)
|
|
1191
1216
|
|
|
1217
|
+
# Restore EC title offsets
|
|
1218
|
+
try:
|
|
1219
|
+
ec_title_offsets = ec.get('title_offsets', {})
|
|
1220
|
+
if ec_title_offsets:
|
|
1221
|
+
ec_ax._top_xlabel_manual_offset_y_pts = float(ec_title_offsets.get('top_y', 0.0) or 0.0)
|
|
1222
|
+
ec_ax._top_xlabel_manual_offset_x_pts = float(ec_title_offsets.get('top_x', 0.0) or 0.0)
|
|
1223
|
+
ec_ax._bottom_xlabel_manual_offset_y_pts = float(ec_title_offsets.get('bottom_y', 0.0) or 0.0)
|
|
1224
|
+
ec_ax._left_ylabel_manual_offset_x_pts = float(ec_title_offsets.get('left_x', 0.0) or 0.0)
|
|
1225
|
+
ec_ax._right_ylabel_manual_offset_x_pts = float(ec_title_offsets.get('right_x', 0.0) or 0.0)
|
|
1226
|
+
ec_ax._right_ylabel_manual_offset_y_pts = float(ec_title_offsets.get('right_y', 0.0) or 0.0)
|
|
1227
|
+
except Exception:
|
|
1228
|
+
pass
|
|
1229
|
+
|
|
1192
1230
|
# Apply EC spines (WASD state already applied above)
|
|
1193
1231
|
if version >= 2:
|
|
1194
1232
|
# Apply EC spines
|
|
@@ -1427,6 +1465,15 @@ def dump_ec_session(
|
|
|
1427
1465
|
'top_x': bool(getattr(ax, '_top_xlabel_on', False)),
|
|
1428
1466
|
'right_y': bool(getattr(ax, '_right_ylabel_on', False)),
|
|
1429
1467
|
}
|
|
1468
|
+
# Title offsets
|
|
1469
|
+
title_offsets = {
|
|
1470
|
+
'top_y': float(getattr(ax, '_top_xlabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
1471
|
+
'top_x': float(getattr(ax, '_top_xlabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
1472
|
+
'bottom_y': float(getattr(ax, '_bottom_xlabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
1473
|
+
'left_x': float(getattr(ax, '_left_ylabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
1474
|
+
'right_x': float(getattr(ax, '_right_ylabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
1475
|
+
'right_y': float(getattr(ax, '_right_ylabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
1476
|
+
}
|
|
1430
1477
|
# Subplot margins
|
|
1431
1478
|
sp = fig.subplotpars
|
|
1432
1479
|
subplot_margins = {
|
|
@@ -1545,6 +1592,7 @@ def dump_ec_session(
|
|
|
1545
1592
|
'tick_direction': tick_direction,
|
|
1546
1593
|
'spines': spines_state,
|
|
1547
1594
|
'titles': titles,
|
|
1595
|
+
'title_offsets': title_offsets,
|
|
1548
1596
|
'mode': getattr(ax, '_is_dqdv_mode', None), # Store dQdV mode flag
|
|
1549
1597
|
'rotation_angle': getattr(fig, '_ec_rotation_angle', 0), # Store rotation angle
|
|
1550
1598
|
'source_paths': list(getattr(fig, '_bp_source_paths', []) or []),
|
|
@@ -1982,6 +2030,19 @@ def load_ec_session(filename: str):
|
|
|
1982
2030
|
ax.set_ylabel('')
|
|
1983
2031
|
ax.yaxis.label.set_visible(False)
|
|
1984
2032
|
|
|
2033
|
+
# Restore title offsets BEFORE positioning titles
|
|
2034
|
+
try:
|
|
2035
|
+
title_offsets = sess.get('title_offsets', {})
|
|
2036
|
+
if title_offsets:
|
|
2037
|
+
ax._top_xlabel_manual_offset_y_pts = float(title_offsets.get('top_y', 0.0) or 0.0)
|
|
2038
|
+
ax._top_xlabel_manual_offset_x_pts = float(title_offsets.get('top_x', 0.0) or 0.0)
|
|
2039
|
+
ax._bottom_xlabel_manual_offset_y_pts = float(title_offsets.get('bottom_y', 0.0) or 0.0)
|
|
2040
|
+
ax._left_ylabel_manual_offset_x_pts = float(title_offsets.get('left_x', 0.0) or 0.0)
|
|
2041
|
+
ax._right_ylabel_manual_offset_x_pts = float(title_offsets.get('right_x', 0.0) or 0.0)
|
|
2042
|
+
ax._right_ylabel_manual_offset_y_pts = float(title_offsets.get('right_y', 0.0) or 0.0)
|
|
2043
|
+
except Exception:
|
|
2044
|
+
pass
|
|
2045
|
+
|
|
1985
2046
|
# Duplicate titles
|
|
1986
2047
|
try:
|
|
1987
2048
|
titles = sess.get('titles', {})
|
|
@@ -2262,6 +2323,15 @@ def dump_cpc_session(
|
|
|
2262
2323
|
'top_xlabel': getattr(ax, '_stored_top_xlabel', ''),
|
|
2263
2324
|
'right_ylabel': getattr(ax2, '_stored_ylabel', ax2.get_ylabel()),
|
|
2264
2325
|
}
|
|
2326
|
+
# Title offsets
|
|
2327
|
+
title_offsets = {
|
|
2328
|
+
'top_y': float(getattr(ax, '_top_xlabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
2329
|
+
'top_x': float(getattr(ax, '_top_xlabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
2330
|
+
'bottom_y': float(getattr(ax, '_bottom_xlabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
2331
|
+
'left_x': float(getattr(ax, '_left_ylabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
2332
|
+
'right_x': float(getattr(ax2, '_right_ylabel_manual_offset_x_pts', 0.0) or 0.0),
|
|
2333
|
+
'right_y': float(getattr(ax2, '_right_ylabel_manual_offset_y_pts', 0.0) or 0.0),
|
|
2334
|
+
}
|
|
2265
2335
|
|
|
2266
2336
|
meta = {
|
|
2267
2337
|
'kind': 'cpc',
|
|
@@ -2324,6 +2394,7 @@ def dump_cpc_session(
|
|
|
2324
2394
|
'wasd_state': wasd_state,
|
|
2325
2395
|
'tick_widths': tick_widths,
|
|
2326
2396
|
'stored_titles': stored_titles,
|
|
2397
|
+
'title_offsets': title_offsets,
|
|
2327
2398
|
'font': {
|
|
2328
2399
|
'size': plt.rcParams.get('font.size'),
|
|
2329
2400
|
'chain': list(plt.rcParams.get('font.sans-serif', [])),
|
|
@@ -2337,6 +2408,30 @@ def dump_cpc_session(
|
|
|
2337
2408
|
if file_data and isinstance(file_data, list) and len(file_data) > 0:
|
|
2338
2409
|
multi_files = []
|
|
2339
2410
|
for f in file_data:
|
|
2411
|
+
def _marker_of(sc, default_val):
|
|
2412
|
+
try:
|
|
2413
|
+
m = getattr(sc, 'get_marker', lambda: default_val)()
|
|
2414
|
+
if m is None:
|
|
2415
|
+
return default_val
|
|
2416
|
+
return m
|
|
2417
|
+
except Exception:
|
|
2418
|
+
return default_val
|
|
2419
|
+
def _alpha_of(sc, default_val=None):
|
|
2420
|
+
try:
|
|
2421
|
+
a = sc.get_alpha()
|
|
2422
|
+
return float(a) if a is not None else default_val
|
|
2423
|
+
except Exception:
|
|
2424
|
+
return default_val
|
|
2425
|
+
def _visible_of(sc, default_val=True):
|
|
2426
|
+
try:
|
|
2427
|
+
return bool(sc.get_visible())
|
|
2428
|
+
except Exception:
|
|
2429
|
+
return default_val
|
|
2430
|
+
def _label_of(sc, default_val=""):
|
|
2431
|
+
try:
|
|
2432
|
+
return sc.get_label() or default_val
|
|
2433
|
+
except Exception:
|
|
2434
|
+
return default_val
|
|
2340
2435
|
file_info = {
|
|
2341
2436
|
'filename': f.get('filename', 'unknown'),
|
|
2342
2437
|
'visible': f.get('visible', True),
|
|
@@ -2344,16 +2439,31 @@ def dump_cpc_session(
|
|
|
2344
2439
|
'x': _np.array(_scatter_xy(f.get('sc_charge', sc_charge))[0]),
|
|
2345
2440
|
'y': _np.array(_scatter_xy(f.get('sc_charge', sc_charge))[1]),
|
|
2346
2441
|
'color': _color_of(f.get('sc_charge')),
|
|
2442
|
+
'size': _size_of(f.get('sc_charge'), 32.0),
|
|
2443
|
+
'alpha': _alpha_of(f.get('sc_charge')),
|
|
2444
|
+
'marker': _marker_of(f.get('sc_charge'), 'o'),
|
|
2445
|
+
'label': _label_of(f.get('sc_charge'), 'Charge capacity'),
|
|
2446
|
+
'visible': _visible_of(f.get('sc_charge')),
|
|
2347
2447
|
},
|
|
2348
2448
|
'discharge': {
|
|
2349
2449
|
'x': _np.array(_scatter_xy(f.get('sc_discharge', sc_discharge))[0]),
|
|
2350
2450
|
'y': _np.array(_scatter_xy(f.get('sc_discharge', sc_discharge))[1]),
|
|
2351
2451
|
'color': _color_of(f.get('sc_discharge')),
|
|
2452
|
+
'size': _size_of(f.get('sc_discharge'), 32.0),
|
|
2453
|
+
'alpha': _alpha_of(f.get('sc_discharge')),
|
|
2454
|
+
'marker': _marker_of(f.get('sc_discharge'), 's'),
|
|
2455
|
+
'label': _label_of(f.get('sc_discharge'), 'Discharge capacity'),
|
|
2456
|
+
'visible': _visible_of(f.get('sc_discharge')),
|
|
2352
2457
|
},
|
|
2353
2458
|
'efficiency': {
|
|
2354
2459
|
'x': _np.array(_scatter_xy(f.get('sc_eff', sc_eff))[0]),
|
|
2355
2460
|
'y': _np.array(_scatter_xy(f.get('sc_eff', sc_eff))[1]),
|
|
2356
2461
|
'color': _color_of(f.get('sc_eff')),
|
|
2462
|
+
'size': _size_of(f.get('sc_eff'), 40.0),
|
|
2463
|
+
'alpha': _alpha_of(f.get('sc_eff')),
|
|
2464
|
+
'marker': _marker_of(f.get('sc_eff'), '^'),
|
|
2465
|
+
'label': _label_of(f.get('sc_eff'), 'Coulombic efficiency'),
|
|
2466
|
+
'visible': _visible_of(f.get('sc_eff')),
|
|
2357
2467
|
}
|
|
2358
2468
|
}
|
|
2359
2469
|
multi_files.append(file_info)
|
|
@@ -2374,9 +2484,9 @@ def dump_cpc_session(
|
|
|
2374
2484
|
|
|
2375
2485
|
|
|
2376
2486
|
def load_cpc_session(filename: str):
|
|
2377
|
-
"""Load a CPC session and reconstruct fig, axes,
|
|
2487
|
+
"""Load a CPC session and reconstruct fig, axes, scatter artists, and file_data.
|
|
2378
2488
|
|
|
2379
|
-
Returns: (fig, ax, ax2, sc_charge, sc_discharge, sc_eff)
|
|
2489
|
+
Returns: (fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_data)
|
|
2380
2490
|
"""
|
|
2381
2491
|
try:
|
|
2382
2492
|
with open(filename, 'rb') as f:
|
|
@@ -2463,12 +2573,47 @@ def load_cpc_session(filename: str):
|
|
|
2463
2573
|
except Exception:
|
|
2464
2574
|
pass
|
|
2465
2575
|
return sc
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
if
|
|
2470
|
-
|
|
2471
|
-
|
|
2576
|
+
# If multi_files exist, rebuild all files and pick the first as primary
|
|
2577
|
+
multi_files = sess.get('multi_files')
|
|
2578
|
+
file_data = []
|
|
2579
|
+
if multi_files and isinstance(multi_files, list) and len(multi_files) > 0:
|
|
2580
|
+
for idx, finfo in enumerate(multi_files):
|
|
2581
|
+
ch_info = finfo.get('charge', {})
|
|
2582
|
+
dh_info = finfo.get('discharge', {})
|
|
2583
|
+
ef_info = finfo.get('efficiency', {})
|
|
2584
|
+
sc_ch = _mk_sc(ax, ch_info, ch_info.get('marker', 'o') or 'o')
|
|
2585
|
+
sc_dh = _mk_sc(ax, dh_info, dh_info.get('marker', 's') or 's')
|
|
2586
|
+
eff_marker = ef_info.get('marker', '^') or '^'
|
|
2587
|
+
sc_ef = _mk_sc(ax2, ef_info, eff_marker)
|
|
2588
|
+
# Respect overall file visibility
|
|
2589
|
+
try:
|
|
2590
|
+
vis_file = bool(finfo.get('visible', True))
|
|
2591
|
+
except Exception:
|
|
2592
|
+
vis_file = True
|
|
2593
|
+
for sc_tmp in (sc_ch, sc_dh, sc_ef):
|
|
2594
|
+
try:
|
|
2595
|
+
sc_tmp.set_visible(sc_tmp.get_visible() and vis_file)
|
|
2596
|
+
except Exception:
|
|
2597
|
+
pass
|
|
2598
|
+
file_data.append({
|
|
2599
|
+
'filename': finfo.get('filename', f'File {idx+1}'),
|
|
2600
|
+
'visible': vis_file,
|
|
2601
|
+
'sc_charge': sc_ch,
|
|
2602
|
+
'sc_discharge': sc_dh,
|
|
2603
|
+
'sc_eff': sc_ef,
|
|
2604
|
+
})
|
|
2605
|
+
# Use the first file as primary artists for interactive menu
|
|
2606
|
+
sc_charge = file_data[0]['sc_charge']
|
|
2607
|
+
sc_discharge = file_data[0]['sc_discharge']
|
|
2608
|
+
sc_eff = file_data[0]['sc_eff']
|
|
2609
|
+
else:
|
|
2610
|
+
# No multi-file info: fall back to single-file series
|
|
2611
|
+
sc_charge = _mk_sc(ax, ch, 'o')
|
|
2612
|
+
sc_discharge = _mk_sc(ax, dh, 's')
|
|
2613
|
+
if 'marker' not in ef:
|
|
2614
|
+
ef['marker'] = '^'
|
|
2615
|
+
sc_eff = _mk_sc(ax2, ef, '^')
|
|
2616
|
+
file_data = None
|
|
2472
2617
|
|
|
2473
2618
|
# Restore spines state (version 2+)
|
|
2474
2619
|
try:
|
|
@@ -2622,6 +2767,16 @@ def load_cpc_session(filename: str):
|
|
|
2622
2767
|
ax2.tick_params(axis='y',
|
|
2623
2768
|
right=wasd_state['right'].get('ticks', True),
|
|
2624
2769
|
labelright=wasd_state['right'].get('labels', True))
|
|
2770
|
+
# Axis title visibility
|
|
2771
|
+
try:
|
|
2772
|
+
if 'bottom' in wasd_state:
|
|
2773
|
+
ax.xaxis.label.set_visible(bool(wasd_state['bottom'].get('title', True)))
|
|
2774
|
+
if 'left' in wasd_state:
|
|
2775
|
+
ax.yaxis.label.set_visible(bool(wasd_state['left'].get('title', True)))
|
|
2776
|
+
if 'right' in wasd_state:
|
|
2777
|
+
ax2.yaxis.label.set_visible(bool(wasd_state['right'].get('title', True)))
|
|
2778
|
+
except Exception:
|
|
2779
|
+
pass
|
|
2625
2780
|
|
|
2626
2781
|
# Minor ticks
|
|
2627
2782
|
if wasd_state.get('top', {}).get('minor') or wasd_state.get('bottom', {}).get('minor'):
|
|
@@ -2660,6 +2815,19 @@ def load_cpc_session(filename: str):
|
|
|
2660
2815
|
except Exception:
|
|
2661
2816
|
pass
|
|
2662
2817
|
|
|
2818
|
+
# Restore title offsets BEFORE restoring titles
|
|
2819
|
+
try:
|
|
2820
|
+
title_offsets = sess.get('title_offsets', {})
|
|
2821
|
+
if title_offsets:
|
|
2822
|
+
ax._top_xlabel_manual_offset_y_pts = float(title_offsets.get('top_y', 0.0) or 0.0)
|
|
2823
|
+
ax._top_xlabel_manual_offset_x_pts = float(title_offsets.get('top_x', 0.0) or 0.0)
|
|
2824
|
+
ax._bottom_xlabel_manual_offset_y_pts = float(title_offsets.get('bottom_y', 0.0) or 0.0)
|
|
2825
|
+
ax._left_ylabel_manual_offset_x_pts = float(title_offsets.get('left_x', 0.0) or 0.0)
|
|
2826
|
+
ax2._right_ylabel_manual_offset_x_pts = float(title_offsets.get('right_x', 0.0) or 0.0)
|
|
2827
|
+
ax2._right_ylabel_manual_offset_y_pts = float(title_offsets.get('right_y', 0.0) or 0.0)
|
|
2828
|
+
except Exception:
|
|
2829
|
+
pass
|
|
2830
|
+
|
|
2663
2831
|
# Restore stored title texts (version 2+)
|
|
2664
2832
|
try:
|
|
2665
2833
|
stored_titles = sess.get('stored_titles', {})
|
|
@@ -2683,26 +2851,46 @@ def load_cpc_session(filename: str):
|
|
|
2683
2851
|
try:
|
|
2684
2852
|
handles1, labels1 = ax.get_legend_handles_labels()
|
|
2685
2853
|
handles2, labels2 = ax2.get_legend_handles_labels()
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2854
|
+
# Filter visible handles only
|
|
2855
|
+
H, L = [], []
|
|
2856
|
+
for h, l in list(zip(handles1, labels1)) + list(zip(handles2, labels2)):
|
|
2857
|
+
try:
|
|
2858
|
+
if hasattr(h, 'get_visible') and not h.get_visible():
|
|
2859
|
+
continue
|
|
2860
|
+
except Exception:
|
|
2861
|
+
pass
|
|
2862
|
+
H.append(h); L.append(l)
|
|
2863
|
+
leg_meta = sess.get('legend', {})
|
|
2864
|
+
xy_in = leg_meta.get('xy_in')
|
|
2865
|
+
vis = bool(leg_meta.get('visible', True))
|
|
2866
|
+
if H and vis:
|
|
2689
2867
|
if xy_in is not None:
|
|
2690
2868
|
fw, fh = fig.get_size_inches()
|
|
2691
2869
|
fx = 0.5 + float(xy_in[0]) / float(fw)
|
|
2692
2870
|
fy = 0.5 + float(xy_in[1]) / float(fh)
|
|
2693
|
-
ax.legend(
|
|
2871
|
+
leg = ax.legend(H, L, loc='center', bbox_to_anchor=(fx, fy), bbox_transform=fig.transFigure, borderaxespad=1.0)
|
|
2694
2872
|
# persist inches on fig for interactive menu
|
|
2695
2873
|
try:
|
|
2696
2874
|
fig._cpc_legend_xy_in = (float(xy_in[0]), float(xy_in[1]))
|
|
2697
2875
|
except Exception:
|
|
2698
2876
|
pass
|
|
2699
2877
|
else:
|
|
2700
|
-
ax.legend(
|
|
2701
|
-
#
|
|
2702
|
-
|
|
2878
|
+
leg = ax.legend(H, L, loc='best', borderaxespad=1.0)
|
|
2879
|
+
# Always hide legend frame to match interactive export behavior
|
|
2880
|
+
try:
|
|
2881
|
+
if leg is not None:
|
|
2882
|
+
leg.set_frame_on(False)
|
|
2883
|
+
except Exception:
|
|
2884
|
+
pass
|
|
2885
|
+
else:
|
|
2886
|
+
try:
|
|
2887
|
+
fig._cpc_legend_xy_in = (float(xy_in[0]), float(xy_in[1])) if xy_in is not None else None
|
|
2888
|
+
except Exception:
|
|
2889
|
+
pass
|
|
2890
|
+
# ensure legend hidden
|
|
2703
2891
|
leg = ax.get_legend()
|
|
2704
2892
|
if leg is not None:
|
|
2705
|
-
leg.set_visible(
|
|
2893
|
+
leg.set_visible(False)
|
|
2706
2894
|
except Exception:
|
|
2707
2895
|
pass
|
|
2708
2896
|
try:
|
|
@@ -2712,7 +2900,7 @@ def load_cpc_session(filename: str):
|
|
|
2712
2900
|
fig.canvas.draw_idle()
|
|
2713
2901
|
except Exception:
|
|
2714
2902
|
pass
|
|
2715
|
-
return fig, ax, ax2, sc_charge, sc_discharge, sc_eff
|
|
2903
|
+
return fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_data
|
|
2716
2904
|
except Exception as e:
|
|
2717
2905
|
import traceback
|
|
2718
2906
|
print(f"Error loading CPC session: {e}")
|